学习channel的时候,笔者问了自己几个问题,个人觉得弄明白了这些问题,至少应该会使用channel了。本文也会从这些问题着手,来讲解channel。
问题1: 什么是channel?是用来做什么的?
1.channel是Go里面的一种类型,它是Go语言为goroutine提供的一种通讯机制,不同的goroutine需要通过channel来相互通讯。 2.channel是有类型的,并且还有方向,可以是单向的,也可以是双向的,类似于unix中的pipe(管道)。 3.channel是通过<-和->来完成读写操作的,channel<-value(表示往channel写数据),<-channel表示从channel读数据。
例子:
channel分为两类,一类是没有缓存的,一类是有缓存的, 它们的使用还是有区别的,下面我们分别来介绍。
问题2: 没有缓存的channel有什么特点,又是怎么使用的呢?
执行 读 操作 value := <-ch 会一直阻塞直到有数据可接收,执行 写 操作 ch <- value 也会一直阻塞直到有 goroutine 对 channel 开始执行接收。同一个gouroutine不能同时支持读写channel也是这个原因导致的。
交互图:
图片来自:https://sanyuesha.com/2017/08/03/go-channel/
从图中我们可以看出,一旦goroutine将数据写入channel中,在数据没被取走的时候,便会被阻塞住,直到数据被取走,才可以释放出来做别的事情。 同样,一个goroutine开始读数据之后,如果channel里没有数据供goroutine拿走,也会阻塞在那里,直到有数据可以被取走为止。
例子1: 同一个goroutine里面不能同时对channel既是读数据,又是写数据,因为channel是需要goroutine之间交互用的。
编译错误:fatal error: all goroutines are asleep - deadlock!goroutine 1 [chan send]:main.main() /tmp/sandbox571395591/prog.go:9 0x60
例子2: channel的正常使用
Output:
例子3: 写数据的阻塞过程演示
Output:
从输出可以看出,goroutine1在写0,1,2的时候,是间隔2秒的,这说明channel在上一次数据没有被读出之前是被阻塞的。(备注:输出里面有一点比较奇怪,先打印写,再打印读,这个是因为打印并不保证同一时间不同goroutine的log顺序。)
补充问题1: 研究到这里,笔者有些奇怪,一旦写数据的goroutine或者读数据的goroutine一直被阻塞到这里,怎么办?有没有超时处理的办法,来解决呢?
goroutinue在被阻塞之后会进入waiting状态,等待被唤醒,但是要是一直没有人唤醒它,就会一直卡死在哪里。 当前的golang并没有提供这种超时处理机制,不过不要担心,我们可以采用select来处理来解决这种超时的问题。(备注:详细方法会在后续select章节给出讲解。)
问题3: 有缓存的channel有什么特点,又是怎么使用的呢?
缓冲channel,有一个队列,写操作只是往队列里面写数据,等到写满数据的时候,如果协程还有数据要写就会阻塞住;读数据的时候,也是从队列里面读取数据,等到队列为空的时候,协程再去读数据的时候,就会被阻塞。(备注:这里的队列是按照时间顺序,先进先出的。)
交互图:
图片来自:https://sanyuesha.com/2017/08/03/go-channel/
从交互图可以看出,两个协程在交互数据的时候,都是通过channel来完成,只要channel没有被填满或者为空,两个协程就可以不停的往里面读写数据,不会发生阻塞。 一旦负责写数据的协程,写数据写的太快,或者负责读数据的协程,读数据读的太慢,channel就会被填满,此时负责写数据的协程就会被阻塞住。 反之,读数据的协程处理很快,写数据的协程写数据很慢,那么channel很快便会被取空,一旦channel变成空的之后,读数据的协程便会被阻塞掉。
例子1:有缓存channel的不阻塞的例子。
output:
从输出可以看出,读数据协程和写数据的协程,读写数据的时间都很顺畅,并没有出现阻塞。(备注:因为是两个协程,所以打印的顺序会显得有些不正常。不过不用担心,它们的正常处理过程是写数据协程先写数据,读数据协程才会读数据。)
例子2:写数据协程被阻塞的情况。
output:
代码故意设置了一个容量为3的channel, 写数据协程的速度很快,一共写了5此数据。读数据的协程延迟两秒来读数据,这样就会导致写数据的协程能够写满channel。
从输出的时间可以看出来,的确如此,写数据的协程在channel是满的时候,便停了下来,等到channel不满之后,才会继续写数据,也就是2s之后。
例子3:读数据协程被阻塞的情况。
output:
从代码可以看出,写数据的协程每隔2s才会写一次数据,而读数据的协程是有数据就读出来。
从输出的结果来看,读数据的协程都得延迟2s才能读出来,而在这2s内,读数据协程是被阻塞的。
参考文档:
Golang Channel用法简编:https://tonybai.com/2014/09/29/a-channel-compendium-for-golang/
Channel:https://draveness.me/golang/concurrency/golang-channel.html
Go Channel: https://colobu.com/2016/04/14/Golang-Channels/
深入理解 Go Channel:http://legendtkl.com/2017/07/30/understanding-golang-channel/
转自本同步公众号:“灰子学技术”