Go并发之channel

ZhDavis · · 1815 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

转自公众号:灰子学技术

原文链接:https://mp.weixin.qq.com/s/PIb-gGBootc6581pHhi5ew

学习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/

转自本同步公众号:“灰子学技术”


有疑问加站长微信联系(非本文作者)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1815 次点击  
加入收藏 微博
被以下专栏收入,发现更多相似内容
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传