RobPike在GoogleI/O2012-GoConcurrencyPatterns里演示了一个例子(daisychain)。视频地址:https://www.youtube.com/watch?v=f6kdp27TYZs
这个例子抽象于“传话游戏”,几个人站成一队,第一个人跟第二个人悄悄说一句话,依次传到最后一个人,看看最后一个人听到的和第一个人说的差别有多大。
代码如下:
packagemain import"fmt" funcpass(left,rightchanint){ left<-1+<-right } funcmain(){ constn=50 leftmost:=make(chanint) right:=leftmost left:=leftmost fori:=0;i<n;i++{ right=make(chanint) //thechainisconstructedfromtheend gopass(left,right)//thefirstgoroutineholds(leftmost,newchan) left=right//thesecondandfollowinggoroutineshold(lastrightchan,newchan) } gofunc(cchanint){c<-1}(right) fmt.Println("sum:",<-leftmost) }这段代码产生了一个单向的管道环,每个节点对输入的值加了1,然后输出给下一个节点,最后到终点leftmost。重点我认为有以下几个:
1,循环中的goroutine;
2,unbufferedchannel的连接和阻塞;
3,goroutine对channel的竞争;
第一点:循环中的goroutine其实很像js中的循环中的异步请求,或者更直观的,像是循环中的setTimeout()。对于main来说,goroutine是异步的,是对线程的细粒度抽象,把它当做一个异步任务就可以了。但是包含了channel的goroutine就有了阻塞的成分。channel也体现了Golang的设计理念之一:Donotcommunicatebysharingmemory;instead,sharememorybycommunicating。
第二点:unbufferedchannel(make(chanint))可以看做是非常短的管子,里面连一个字节都不能存储,必须先找到两端的输入和输出,不然就会出问题(阻塞)。比如下面代码:
funcmain(){ c:=make(chanint) c<-1 fmt.Println(<-c) } //fatalerror:allgoroutinesareasleep-deadlock!上面的代码中,c<-1这一行给channel输入了数据,但是此时还没有接收者(代码是同步执行的),因此卡死在这儿了。
那如果先给channel指定了输出,然后再输入数据呢?结果是一样的,只有接收者没有输入者,一样卡死。
改成这样就可以了:
funcmain(){ c:=make(chanint) gofunc(){fmt.Println(<-c)}() c<-1 }这里先在goroutine里指定了Println作为接收者,然后给了输入。
可以理解为:channel不能同时输入和输出,<-c<-1会报错(可能Golang觉得这样是没有意义的);指定输入和输出必须写在两行,而代码的同步执行决定了不能同时指定输入和输出,因此只能用goroutine。实际上channel本身也是为了goroutine间的通讯。
bufferedchannel就比较好理解,是带有容器的管道,可以存储一定数量的数据。但是当容器满的时候,表现就和unbufferedchannel一样,会阻塞。
第三点:第一块代码里产生的管道环是从终点开始连接起的,最后一根管道实际上是数据流的第一节管道。在这个环刚完成的时候,所有管道都是空的,没有输入。这时所有的goroutine都被阻塞了,倒数第三行的go给了第一个管道一个输入,于是这点数据就流到了最后。
本文内容总结:
原文链接:https://www.cnblogs.com/jasonxuli/p/6861791.html