Channel¶
我们知道可以通过go关键字来开启一个goroutine,我们的样例代码逻辑很简单,都是在各个goroutine各自处理自己的逻辑,但有时候我们需要不同的goroutine之间能够通信,这里就要用到channel。
channel是什么¶
官方定义: Channels are a typed conduit through which you can send and receive values with the channel operator.
即Channel是一个可以收发数据的管道。
channel初始化¶
channel的声明方式如下:
| Go | |
|---|---|
nil,我们要使用还要配合make函数来对其初始化,之后才可以在程序中使用该管道。
或者我们可以直接一步完成声明和初始化,如下:
| Go | |
|---|---|
channel操作¶
channel的操作主要有以下几种
| Go | |
|---|---|
close(ch)这个操作,管道用完了,需要对其进行关闭,避免程序一直在等待以及资源的浪费。但是关闭的管道,仍然可以从中接收数据,在接收完成后,继续接收的数据永远是零值。
看下面例子:
| Go | |
|---|---|
5的int类型的管道,向管道里写入一个1之后,将管道关闭,然后开启一个gortoutine从管道读取数据,读取5次,可以看到即便管道关闭之后,他仍然可以读取数据,在读完数据之后,将一直读取零值。
但是,上述读取方式还有一个问题?比如我们创建一个int类型的channel,我们需要往里面写入零值,用另一个goroutine读取,此时我们就无法区两种常用的读取方式
判定读取¶
还是以上面的例子来看,稍作修改
| Go | |
|---|---|
channel数据的时候,用ok做了判断,当管道内还有数据能读取的时候,ok为true,当管道关闭后,ok为false。需要注意的是,如果channel中还有数据,即便已经被关闭,ok仍然为true。
for range读取¶
在上面例子中,我们明确了读取的次数是5次,但是我们往往在更多的时候,是不明确读取次数的,只是在channel的一端读取数据,有数据我们就读,直到另一端关闭了这个channel,这样就可以用for range这种优雅的方式来读取channel中的数据了。
| Go | |
|---|---|
goroutine往channel里写了两个数据1和2,然后关闭,子goroutine也只能读取到1和2。这里在主goroutine关闭了channel之后,子goroutine里的for range循环才会结束,即ok为false的时候。
双向channel和单向channel¶
channel根据其功能又可以分为双向channel和单向channel,双向channel即可发送数据又可接收数据,单向channel要么只能发送数据,要么只能接收数据。
定义单向读channel
channel
注意写channel与读channel在定义的时候只是<-的位置不同,前者在chan关键字后面,后者在chan关键字前面。
代码示例:
channel ch,分别定义两个单向channel类型SChannel和RChannel ,根据别名类型给ch定义两个别名send和rec,一个只用于发送,一个只用于读取。
扩展¶
channel非常重要,Go语言中有个重要思想:不以共享内存来通信,而以通信来共享内存。
说得更直接点,协程之间可以利用channel来传递数据,如下的例子,可以看出父子协程如何通信的,父协程通过channel拿到了子协程执行的结果。
| Text Only | |
|---|---|
channel又分为两类:有缓冲channel 和无缓冲channel,这个在前面的代码示例中也有简单的描述了。为了协程安全,无论是有无缓冲的channel,内部都会有一把锁来控制并发访问。同时channel底层一定有一个队列,来存储数据。
无缓冲channel可以理解为同步模式,即写入一个,如果没有消费者在消费,写入就会阻塞。
有缓冲channel可以理解为异步模式。即写入消息之后,即使还没被消费,只要队列没满,就可继续写入。如图所示:
这里可能会问,如果有缓冲channel队列满了,那不就退化到同步了么?是的,如果队列满了,发送还是会阻塞。
但是我们来反向思考下,如果有缓冲channel长期都处于满队列情况,那何必用有缓冲。所以预期在正常情况下,有缓冲channel都是异步交互的。
channel实现锁操作¶
前面分析了当缓冲队列满了以后,继续往channel里面写数据,就会阻塞,那么利用这个特性,我们可以实现一个goroutine之间的锁。(对并发安全比较模糊的可以把后面sync小节看完再来看这里)
| Go | |
|---|---|
| Text Only | |
|---|---|
ch <- true和<- ch就相当于一个锁,将 *num = *num + 1这个操作锁住了。因为ch管道的容量是1,在每个add函数里都会往channel放置一个true,直到执行完+1操作之后才将channel里的true取出。由于channel的size是1,所以当一个goroutine在执行add函数的时候,其他goroutine执行add函数,执行到ch <- true的时候就会阻塞,*num = *num + 1不会成功,直到前一个+1操作完成,<-ch,读出了管道的元素,这样就实现了并发安全
小结¶
- 关闭一个未初始化的
channel会产生panic; channel只能被关闭一次,对同一个channel重复关闭会产生panic;- 向一个已关闭的
channel发送消息会产生panic; - 从一个已关闭的
channel读取消息不会发生panic,会一直读取所有数据,直到零值; channel可以读端和写端都可有多个goroutine操作,在一端关闭channel的时候,该channel读端的所有goroutine都会收到channel已关闭的消息;channel是并发安全的,多个goroutine同时读取channel中的数据,不会产生并发安全问题。
channel在我们的并发编程中发挥着巨大作用,使用起来也很方便,关于channel的具体实现原理可以到后面的Go语言原理篇学习。本章只介绍channel的具体用法。

