一个Golang例子:for + goroutine + channel
本文内容纲要:
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