关键词:
背景
Golang与其他语言最大的区别是什么呢?在我看来,一个是goroutine,另一个就是通道channel了。
其他语言,一般通过共享内存的方式实现不同线程间的通信,也就是说,把数据放在共享内存以供多个线程来使用。这种方法思路简单,但却使得并发控制变得复杂和低效。Golang不建议使用这种方式(虽然也提供了这种传统方式),而是推荐使用通道,也就是channel。
详解
基础操作
声明通道
声明一个通道类型如下:
var intChan chan int
初始化通道
通道是一种引用类型,和切片、字典相同。初始化引用类型,都需要用到make关键字,下面代码分别创建了一个非缓冲通道和缓冲通道。
make(chan int)
make(chan int, 5)
发送值到通道
向通道发送一个值。
intChan <-3
从通道接收值
从通道取出一个值。第二种方式更安全,当通道被关闭时,num仍然为0,无法通过num来区分是通道出来的值为0,还是无效值为0,这时就可以通过bool型的ok值来判断是否有效了。
num := <-intChan
num, ok := <-intChan
关闭通道
使用close函数可关闭通道,建议从发送方关闭通道。
关闭通道后,如果尝试向通道发送数据,则会引起运行时恐慌;如果尝试从通道接收值,无论通道是否还有值都会立即返回,可以通过第二个值判断返回值是否有效。
func testM()
intChan := make(chan int, 1)
intChan <- 3
close(intChan)
num, ok := <-intChan
fmt.Printf("get num: %d, ok: %t\\n", num, ok)
num, ok = <-intChan
fmt.Printf("get num: %d, ok: %t\\n", num, ok)
time.Sleep(time.Second)
通道类型
通过make初始化通道时,如果指定了第二个参数通道容量为非零时,那么该通道就是缓冲通道,否则就是非缓冲通道。
非缓冲通道
非缓冲通道,顾名思义,通道无法缓冲任何值,也就是说操作都是同步的。发送方和接收方必须都准备好,否则先执行的发送或者接受goroutine就会被阻塞。
func test1()
intChan := make(chan int)
go func()
num := <-intChan
fmt.Printf("get num: %d\\n", num)
()
intChan <- 3
fmt.Printf("send num: %d\\n", 3)
time.Sleep(2 * time.Second)
输出:
get num: 3
send num: 3
上面的例子中,主goroutine发送数据到通道,另外一个goroutine从通道接收数据。我们再加上一点等待时间,看是否会阻塞。
func test2()
fmt.Printf("start\\n")
intChan := make(chan int)
go func()
num := <-intChan
fmt.Printf("get num: %d\\n", num)
()
time.Sleep(2 * time.Second)
intChan <- 3
fmt.Printf("send num: %d\\n", 3)
time.Sleep(2 * time.Second)
我们在发送数据到通道之前,休眠了2秒。程序开始执行后,等待了2秒之后,才有get num的打印,说明这是才收到数据,也就证明了的确会被阻塞。当然,在接收通道之前休眠,也有类似的效果,发送处会被阻塞。
无法缓冲,那么下面的程序会输出什么结果呢?
func test3()
intChan := make(chan int)
intChan <- 3
num := <-intChan
fmt.Printf("get num: %d\\n", num)
time.Sleep(5 * time.Second)
换成缓冲通道后,结果又如何呢?
缓冲通道
缓冲通道,也就是说通道可以缓存一定的数据,那么也就可以异步操作了。可以把缓冲通道理解为FIFO,先进先出。
func test2()
fmt.Printf("start\\n")
intChan := make(chan int, 5)
go func()
for i := 0; i < 6; i++
num, ok:= <-intChan
fmt.Printf("get num: %d, %t\\n", num, ok)
()
for i := 0; i < 6; i++
intChan <- i
time.Sleep(2 * time.Second)
输出:
start
get num: 0, true
get num: 1, true
get num: 2, true
get num: 3, true
get num: 4, true
可以通过len函数查看通道当前的长度,cap函数查看通道的容量。
func test2()
fmt.Printf("start\\n")
intChan := make(chan int, 5)
for i := 0; i < 5; i++
intChan <- i
fmt.Printf("len: %d, cap: %d\\n", len(intChan), cap(intChan))
time.Sleep(5 * time.Second)
fmt.Printf("end\\n")
输出:
start
len: 1, cap: 5
len: 2, cap: 5
len: 3, cap: 5
len: 4, cap: 5
len: 5, cap: 5
end
处理通道数据
for语句
使用for循环和range来接收通道数据,即使通道被关闭,通道内的数据也能正确的被接收处理,待通道内数据都被接收完后,for语句就会立即结束。可以省掉判断接收值是否有效的代码,更加简洁。
func test1()
intChan := make(chan int, 10)
go func()
for i := range intChan
fmt.Printf("num: %d\\n", i)
fmt.Printf("receiving ends\\n")
()
for i := 0; i < 5; i++
intChan <-i
close(intChan)
time.Sleep(10 * time.Second)
select语句
select语句可以用来接收和发送通道,其语法形式和switch语句类似,只是select关键字后直接跟大括号。
select语句tip1
select选择不阻塞的case分支时,具有随机性。
func test1()
times := 10
intChan := make(chan int, times)
for i := 0; i < times; i++
select
case intChan <-0:
case intChan <-1:
case intChan <-2:
for i := 0; i < times; i++
fmt.Printf("num: %d\\n", <-intChan)
time.Sleep(1 * time.Second)
输出如下。
num: 1
num: 2
num: 1
num: 2
num: 0
num: 0
num: 0
num: 1
num: 1
num: 1
select语句tip2
无论选择哪条case语句,所有case语句里,通道符两边的表达式都会被求值。且执行顺序是从上到下,从左到右。
var intChan1 chan int
var intChan2 chan int
var channels = []chan intintChan1, intChan2
var nums = []int1, 2
func getChan(i int) chan int
fmt.Printf("channel[%d]\\n", i)
return channels[i]
func getNum(i int) int
fmt.Printf("nums[%d]\\n", i)
return nums[i]
func test2()
fmt.Printf("start\\n")
// channels[0] = make(chan int, 1)
// channels[1] = make(chan int, 1)
select
case getChan(0) <-getNum(0):
fmt.Printf("case 0\\n")
case getChan(1) <-getNum(1):
fmt.Printf("case 1\\n")
default:
fmt.Println("default case")
time.Sleep(1 * time.Second)
fmt.Printf("end\\n")
输出:
start
channel[0]
nums[0]
channel[1]
nums[1]
default case
end
两个通道都未被初始化,发送数据到通道会被阻塞,所以最终选择了执行default分支。但前面case两边的表达式都被求值了。
再分别取消示例中对通道初始化的注释,再执行,可以看到无论选择哪条分支,case两边的表达式都会被求值。
select语句tip3
如果所有分支都阻塞,且没有default分支,那么该select语句就会被阻塞。
func test3()
fmt.Printf("start\\n")
// A deadlock will happen
select
case getChan(0) <-getNum(0):
fmt.Printf("case 0\\n")
case getChan(1) <-getNum(1):
fmt.Printf("case 1\\n")
time.Sleep(1 * time.Second)
fmt.Printf("end\\n")
输出:
start
channel[0]
nums[0]
channel[1]
nums[1]
fatal error: all goroutines are asleep - deadlock!
由于是主goroutine,且没有其他goroutine,就发生了死锁。
参考
https://www.cnblogs.com/wt645631686/p/9657711.html
《Go并发编程实践》
go语言并发与通道的运用(代码片段)
在go语言中我们可以使用goroutine开启并发。goroutine是轻量级线程,goroutine的调度是由Golang运行时进行管理的。goroutine语法格式:go函数名(参数列表)实例1:packagemainimport("fmt""time")funcsay(sstring)fori:=0;i< 查看详情
go语言通道(代码片段)
背景Golang与其他语言最大的区别是什么呢?在我看来,一个是goroutine,另一个就是通道channel了。其他语言,一般通过共享内存的方式实现不同线程间的通信,也就是说,把数据放在共享内存以供多个线程... 查看详情
go的通道channel(代码片段)
1、前言作为Go语言最有特色的数据类型,通道(channel)完全可以与goroutine(也可称为go程)并驾齐驱,共同代表Go语言独有的并发编程模式和编程哲学。Don’tcommunicatebysharingmemory;sharememorybycommunicating.(... 查看详情
go的通道channel(代码片段)
1、前言作为Go语言最有特色的数据类型,通道(channel)完全可以与goroutine(也可称为go程)并驾齐驱,共同代表Go语言独有的并发编程模式和编程哲学。Don’tcommunicatebysharingmemory;sharememorybycommunicating.(... 查看详情
go语言通道(chan)——goroutine之间通信的管道(代码片段)
Go语言通道(chan)——goroutine之间通信的管道如果说goroutine是Go语言程序的并发体的话,那么channels就是它们之间的通信机制。一个channels是一个通信机制,它可以让一个goroutine通过它给另一个goroutine发送值信息。每个channel... 查看详情
go-通道(代码片段)
通道类型通道(Channel)是Go语言中一种非常独特的数据结构。它可用于在不同Goroutine之间传递类型化的数据,并且是并发安全的。 通道类型的表示方法很简单,仅由两部分组成chanT在这个类型字面量中,左边是代表... 查看详情
golang✔️走进go语言✔️第十八课通道关闭&工作池(代码片段)
【Golang】✔️走进Go语言✔️第十八课通道关闭&工作池概述通道关闭工作池概述Golang是一个跨平台的新生编程语言.今天小白就带大家一起携手走进Golang的世界.(第18课)通道关闭通道关闭的意思是该通道不允许写入数据.这个方... 查看详情
golang✔️走进go语言✔️第十八课通道关闭&工作池(代码片段)
【Golang】✔️走进Go语言✔️第十八课通道关闭&工作池概述通道关闭工作池概述Golang是一个跨平台的新生编程语言.今天小白就带大家一起携手走进Golang的世界.(第18课)通道关闭通道关闭的意思是该通道不允许写入数据.这个方... 查看详情
go语言学习笔记—进阶—并发编程:通道(channel)——各种各样的通道(代码片段)
单向通道在声明通道时,我们可以设置只发送或只接收。这种被约束操作方向的通道称为单向通道。声明单向通道只发送:chan<-,只接收:<-chanvar通道实例chan<-元素类型//只发送数据var通道实例<-chan元素... 查看详情
golang✔️走进go语言✔️第十六课协程&通道(代码片段)
【Golang】✔️走进Go语言✔️第十六课协程&通道概述协程并发vs并行进程vs线程vs协程协程并发通道创建通道通道同步概述Golang是一个跨平台的新生编程语言.今天小白就带大家一起携手走进Golang的世界.(第16课)协程协程(Coroutine)... 查看详情
golang✔️走进go语言✔️第十六课协程&通道(代码片段)
【Golang】✔️走进Go语言✔️第十六课协程&通道概述协程并发vs并行进程vs线程vs协程协程并发通道创建通道通道同步概述Golang是一个跨平台的新生编程语言.今天小白就带大家一起携手走进Golang的世界.(第16课)协程协程(Coroutine)... 查看详情
七天入门go语言通道&goroutine|第四天并发编程(代码片段)
1.前言在go社区有这样一句话不要通过共享内存来通信,而是通过通信来共享内存。go官方是建议使用管道通信的方式来进行并发。通道是用于协程间交流的通信载体。严格地来说,通道就是数据传输的管道,数据通过... 查看详情
并发——轻量级线程,通道,单向通道(代码片段)
1、轻量级线程goroutine是Go语言中的轻量级线程实现,由Go运行时(runtime)管理。Go程序会智能地将goroutine中的任务合理地分配给每个CPU。Go程序从main包的main()函数开始,在程序启动时,Go程序就会为main()函数创建一个默认的goroutin... 查看详情
goplgoroutine和通道(代码片段)
Go语言的并发编程风格Go有两种并发编程风格:goroutine和通道(chennle),支持通信顺序进程(CommunicatingSequentialProcess,CSP),CSP是一个并发的模式,在不同的执行体(goroutine)之间传递值,但是变量本身局限于单一的执行体。共享... 查看详情
go语言学习笔记—进阶—并发编程:通道(channel)——使用channel收发数据(代码片段)
创建channel之后,我们可以使用channel进行数据收发操作。使用channel发送(填充)数据使用特殊的操作符<-,把数据通过channel发送。格式通道变量<-值通道变量是上文通过make创建的通道实例;值可以是变量... 查看详情
go语言通道(代码片段)
背景Golang与其他语言最大的区别是什么呢?在我看来,一个是goroutine,另一个就是通道channel了。其他语言,一般通过共享内存的方式实现不同线程间的通信,也就是说,把数据放在共享内存以供多个线程... 查看详情
go源码阅读——chan.go(代码片段)
【博文目录>>>】【项目地址>>>】chan.go是go语言通道实现,通道结构的定义,接收和发送的操作都此文件中实现。通道的结构hchan是通道表示的基本结构,其内容表示如下:一些特殊情况当dataqsiz=0时... 查看详情
云原生时代崛起的编程语言go并发编程实战(代码片段)
Go语言是天然并发利器,通过通信来实现内存共享而不是通过共享内存来通信,本篇从了解Go的并发哲学、理论及并发原语开始,之后用一个个Go代码示例认识Go的协程、通道、定时器、互斥锁、池化、原生操作等十几个并发编程... 查看详情