Golang, 以17个简短代码片段,切底弄懂 channel 基础
本文内容纲要:
(原创出处为本博客:http://www.cnblogs.com/linguanh/)
前序:
因为打算自己搞个基于Golang的IM服务器,所以复习了下之前一直没怎么使用的协程、管道等高并发编程知识。发现自己的channel这块,也就是管道,实在是有些混乱。然后对着文档,边参照官网例子和在编译器测试,总结了下面这17个例子,设置为简短的片段,是为了免得混淆太多,阻碍理解。内含注释丰富,复制粘贴就能编译使用。
这里立个flag,有错误欢迎指出,只要你跟着敲完这17个例子,channel的基础绝对可以掌握!
基本概念:
关于管道Channel:
Channels用来同步并发执行的函数并提供它们某种传值交流的机制。
Channels的一些特性:通过channel传递的元素类型、容器(或缓冲区)和传递的方向由“<-”操作符指定。
c<-123,把值123输入到管道c,<-c,把管道c的值读取到左边,value:=<-c,这样就是读到value里面。
管道分类:
****无缓冲的与有缓冲channel有着重大差别,那就是一个是同步的一个是非同步的。
比如
c1:=make(chanint)无缓冲
c2:=make(chanint,1)有缓冲
例如:c1<-1
无缓冲:不仅仅是向c1通道放1,而是一直要等有别的携程<-c1接手了这个参数,那么c1<-1才会继续下去,要不然就一直阻塞着。
有缓冲:c2<-1则不会阻塞,因为缓冲大小是1(其实是缓冲大小为0),只有当放第二个值的时候,第一个还没被人拿走,这时候才会阻塞。
例子s
演示无缓存和有缓冲的channel的样子
1functest0(){
2/**演示无缓存和有缓冲的channel的样子*/
3done:=make(chanbool)/**无缓冲*/
4done1:=make(chanbool,1)/**有缓冲*/
5println(done,done1)
6}
演示无缓冲在同一个main里面的死锁例子
1functest1(){
2/**编译错误deadlock,阻死main进程*/
3/**演示无缓冲在同一个main里面的死锁例子*/
4done:=make(chanbool)
5done<-true/**这句是输入值,它会一直阻塞,等待读取*/
6<-done/**这句是读取,但是在上面已经阻死了,永远走不到这里*/
7println("完成")
8}
演示仅有输入语句,但没读取语句的死锁例子
1functest2(){
2/**编译错误deadlock,阻死main进程*/
3/**演示仅有输入语句,但没读取语句的死锁例子*/
4done:=make(chanbool)
5done<-true/**输入,一直等待读取,哪怕没读取语句*/
6println("完成")
7}
演示仅有读取语句,但没输入语句的死锁例子
1functest3(){
2/**编译错误deadlock,阻死main进程*/
3/**演示仅有读取语句,但没输入语句的死锁例子*/
4done:=make(chanbool)
5<-done/**读取输出,前面没有输入语句,done是empty的,所以一直等待输入*/
6
7println("完成")
8}
演示,协程的阻死,不会影响main
1functest4(){
2/**编译通过*/
3/**演示,协程的阻死,不会影响main*/
4done:=make(chanbool)
5gofunc(){
6<-done/**一直等待*/
7}()
8println("完成")
9/**
10*控制台输出:
11*完成
12*/
13}
在test4的基础上,无缓冲channel在协程goroutine里面阻塞死
1functest5(){
2/**编译通过*/
3/**在test4的基础上,无缓冲channel在协程goroutine里面阻塞死*/
4done:=make(chanbool)
5gofunc(){
6println("我可能会输出哦")/**阻塞前的语句*/
7done<-true/**这里阻塞死,但是上面那句有可能输出,见test3的结论*/
8println("我永远不会输出")
9<-done/**这句也不会走到,除非在别的协程里面读取,或者在main*/
10}()
11println("完成")
12}
编译通过,在test5的基础上演示,延时main的跑完
1functest6(){
2/**编译通过,在test5的基础上演示,延时main的跑完*/
3done:=make(chanbool)
4gofunc(){
5println("我可能会输出哦")
6done<-true/**这里阻塞死*/
7println("我永远不会输出")
8<-done/**这句也不会走到*/
9}()
10time.Sleep(time.Second*1)/**加入延时1秒*/
11println("完成")
12/**
13*控制台输出:
14*我可能会输出哦
15*完成
16*/
17/**
18*结论:
19*如果在goroutine中阻塞死,也可能不会把阻塞语句前的内容输出,
20*因为main已经跑完了,所以延时一会,等待goroutine
21*/
22}
演示无缓冲channel在不同的位置里面接收填充和接收
1functest7(){
2/**编译通过,演示无缓冲channel在不同的位置里面接收填充和接收*/
3done:=make(chanbool)
4gofunc(){
5done<-true/**直到,<-done执行,否则这里阻塞死*/
6println("我永远不会输出,除非<-done执行")
7
8}()
9<-done/**这里接收,在输出完成之前,那么上面的语句将会走通*/
10println("完成")
11/**
12*控制台输出:
13*我永远不会输出,除非<-done执行
14*完成
15*/
16}
演示无缓冲channel在不同地方接收的影响
1functest8(){
2/**编译通过,演示无缓冲channel在不同地方接收的影响*/
3done:=make(chanbool)
4gofunc(){
5done<-true/**直到,<-done执行,否则这里阻塞死*/
6println("我永远不会输出,除非<-done执行")
7}()
8println("完成")
9<-done/**这里接收,在输出完成之后*/
10/**
11*控制台输出:
12*完成
13*我永远不会输出,除非<-done执行
14*/
15}
没缓存的channel使用close后,不会阻塞
1functest9(){
2/**编译通过*/
3/**演示,没缓存的channel使用close后,不会阻塞*/
4done:=make(chanbool)
5close(done)
6//done<-true/**关闭了的,不能再往里面输入值*/
7<-done/**这句是读取,但是在上面已经关闭channel了,不会阻死*/
8println("完成")
9}
没缓存的channel,在goroutine里面使用close后,不会阻塞
1functest10(){
2/**编译通过*/
3/**演示,没缓存的channel,在goroutine里面使用close后,不会阻塞*/
4done:=make(chanbool)
5gofunc(){
6close(done)
7}()
8//done<-true/**关闭了的,不能再往里面输入值*/
9<-done/**这句是读取,但是在上面已经关闭channel了,不会阻死*/
10println("完成")
11}
有缓冲的channel不会阻塞的例子
1functest11(){
2/**编译通过*/
3/**有缓冲的channel不会阻塞的例子*/
4done:=make(chanbool,1)
5done<-true
6<-done
7println("完成")
8}
有缓冲的channel会阻塞的例子
1functest12(){
2/**编译通过*/
3/**有缓冲的channel会阻塞的例子*/
4done:=make(chanbool,1)
5//done<-true/**注释这句*/
6<-done/**虽然是有缓冲的,但是在没输入的情况下,读取,会阻塞*/
7println("完成")
8}
有缓冲的channel会阻塞的例子
1functest13(){
2/**编译不通过*/
3/**有缓冲的channel会阻塞的例子*/
4done:=make(chanbool,1)
5done<-true
6done<-false/**放第二个值的时候,第一个还没被人拿走,这时候才会阻塞,根据缓冲值而定*/
7println("完成")
8}
有缓冲的channel不会阻塞的例子
1functest14(){
2/**编译通过*/
3/**有缓冲的channel不会阻塞的例子*/
4done:=make(chanbool,1)
5done<-true/**不会阻塞在这里,等待读取*/
6
7println("完成")
8}
有缓冲的channel,如果在goroutine中使用,一定要做适当的延时,否则会输出来不及,因为main已经跑完了,所以延时一会,等待goroutine
1functest15(){
2/**编译通过*/
3/**有缓冲的channel在goroutine里面的例子*/
4done:=make(chanbool,1)
5gofunc(){
6/**不会阻塞*/
7println("我可能会输出哦")
8done<-true/**如果把这个注释,也会导致<-done阻塞*/
9println("我也可能会输出哦")
10<-done
11println("别注释done<-true哦,不然我就输出不了了")
12}()
13time.Sleep(time.Second*1)/**1秒延时,去掉就可能上面的都不会输出也有可以输出,routine调度*/
14println("完成")
15/**
16*控制台输出:
17*我可能会输出哦
18*我也可能会输出哦
19*完成
20*/
21/**
22*结论:
23*有缓冲的channel,如果在goroutine中使用,一定要做适当的延时,否则会输出来不及,
24*因为main已经跑完了,所以延时一会,等待goroutine
25*/
26}
多channel模式
1funcgetMessagesChannel(msgstring,delaytime.Duration)<-chanstring{
2c:=make(chanstring)
3gofunc(){
4fori:=1;i<=3;i++{
5c<-fmt.Sprintf("%s%d",msg,i)
6time.Sleep(time.Millisecond*delay)/**仅仅起到,下一次的c在何时输入*/
7}
8}()
9returnc
10}
11
12functest16(){
13/**编译通过*/
14/**复杂的演示例子*/
15/**多channel模式*/
16c1:=getMessagesChannel("第一",600)
17c2:=getMessagesChannel("第二",500)
18c3:=getMessagesChannel("第三",5000)
19
20/**层层限制阻塞*/
21/**这个for里面会造成等待输入,c1会阻塞c2,c2阻塞c3*/
22/**所以它总是,先输出c1然后是c2最后是c3*/
23fori:=1;i<=3;i++{
24/**每次循环提取一轮,共三轮*/
25println(<-c1)/**除非c1有输入值,否则就阻塞下面的c2,c3*/
26println(<-c2)/**除非c2有输入值,否则就阻塞下面的c3*/
27println(<-c3)/**除非c3有输入值,否则就阻塞进入下一轮循环,反复如此*/
28}
29/**
30*这个程序的运行结果,首轮的,第一,第二,第三很快输出,因为
31*getMessagesChannel函数的延时在输入值之后,在第二轮及其之后
32*因为下一个c3要等到5秒后才能输入,所以会阻塞第二轮循环的开始5秒,如此反复。
33*/
34/**修改:如果把getMessagesChannel里面的延时,放在输入值之前,那么c3总是等待5秒后输出*/
35}
在test15基础修改的,复杂演示例,多channel的选择,延时在输入之后的情况
1functest17(){
2/**编译通过*/
3/**在test15基础修改的,复杂演示例子*/
4/**多channel的选择,延时在输入之后的情况*/
5c1:=getMessagesChannel("第一",600)
6c2:=getMessagesChannel("第二",500)
7c3:=getMessagesChannel("第三",5000)
8/**3x3次循环,是9*/
9/**select总是会把最先完成输入的channel输出,而且,互不限制*/
10/**c1,c2,c3每两个互不限制*/
11fori:=1;i<=9;i++{
12select{
13casemsg:=<-c1:
14println(msg)
15casemsg:=<-c2:
16println(msg)
17casemsg:=<-c3:
18println(msg)
19}
20}
21/**
22*这个程序的运行结果:
23*第二1,第三1,第一1,第二2,第一2,第二3,第一3,第三2,第三3
24*/
25/**分析:前3次输出,“第一”,“第二”,“第三”,都有,而且
26*是随机顺序输出,因为协程的调度,第4,5,6次,由于“第二”只延时500ms,
27*比600ms和5000ms都要小,那么它先输出,然后是“第一”,此时“第三”还不能输出,
28*因为它还在等5秒。此时已经输出5次,再过500ms,"第三"的5秒还没走完,所以继续输出"第一",
29*再过100ms,500+100=600,"第二"也再完成了一次,那么输出。至此,"第一"和"第二"已经
30*把管道的3个值全部输出,9-7=2,剩下两个是"第三"。此时,距离首次的5000ms完成,
31*还有,500-600-600=3800ms,达到后,"第三"将输出,再过5秒,最后一次"第三输出"
32*/
33}
欢迎转载
本文内容总结:
原文链接:https://www.cnblogs.com/linguanh/p/6248301.html