您的位置 首页 > 德国留学

channel,Golang之通道

大家好,今天来为大家解答channel这个问题的一些问题点,包括Golang之通道也一样很多人还不知道,因此呢,今天就来为大家分析分析,现在让我们一起来看看吧!如果解决了您的问题,还望您关注下本站哦,谢谢~

Go中goroutine运行在相同的地址空间中,因此访问共享内存对象必须做好同步机制。那goroutine之间的通信如何实现呢?Golang中提供一个很好的通信机制channel。通过channel可以发送或者接收值,channel会有自己的类型chan类型,定义一个通道时,需要定义在通道中传输的数据类型。

定义一个channel,必须使用make函数:

channel,Golang之通道

c1:=make(chanint)\nc2:=make(chanstruct{})\nc3:=make(chaninterface{})\nc4:=make(chanstring)

channel通过操作符<-实现发送和接收数据

c1<-12//将12发送到通道c1中\nv1:=<-c1//从c1通道中获取值并赋给v1\n<-c1//这个语句表示结果会被丢掉

下面看下示例:

packagemain\n\nimport(\n"testing"\n"time"\n)\n\nfuncsum(a[]int,cchanint,t*testing.T){\nvarsint\nfor_,i:=rangea{\ns=s+i\n}\nt.Log("计算完成,睡眠一秒")\ntime.Sleep(time.Second)\nt.Log("睡眠结束,向通道发送计算结果")\nc<-s\n}\n\nfuncTestChannel(t*testing.T){\nl1:=[]int{12,3,4,5,6}\nvarcha1=make(chanint)//定义一个无缓存通道\ngosum(l1,cha1,t)\nt.Log("调用并发,等待通道回传数据")\nr1:=<-cha1\nt.Log("获取计算结果:",r1)\n}

输出:

===RUNTestChannel\nchannel_test.go:23:调用并发,等待通道回传数据\nchannel_test.go:13:计算完成,睡眠一秒\nchannel_test.go:15:睡眠结束,向通道发送计算结果\nchannel_test.go:25:获取计算结果:30\n---PASS:TestChannel(1.00s)\nPASS

以上代码中通过对通道通信,完成了数据共享,运行代码时会发现,程序会阻塞在24行,就是从通道获取信息的位置,这是由于,同步通道会在发数据或收数据时都必须在通道完整的情况下,如果有发没收,发送就会阻塞,如果有收没发,收取就会阻塞,通过这个逻辑可以进行并发线程之间的同步。

通过这种同步可以建立一个管道:

packagemain\n\nimport(\n"testing"\n"time"\n)\n\nfuncTestChannel2(t*testing.T){\n//创建两个无缓冲通道\nnumL:=make(chanint)\nsqL:=make(chanint)\n\ngofunc(){\nfori:=0;;i++{//创建无限遍历逻辑\nnumL<-i//将i送入通道\ntime.Sleep(time.Second)//每一秒产生一个数\n}\n}()\ngofunc(){\nfor{\nsqL<-(<-numL)*2//获取numL通道的数据进行乘2后加入到sqL通道\n}\n}()\n\nfor{//通过遍历不断从sqL通道中读取数据\nt.Log("sqL管道出来的值:",<-sqL)\n}\n}

输出:

===RUNTestChannel2\nchannel_test.go:26:sqL管道出来的值:0\nchannel_test.go:26:sqL管道出来的值:2\nchannel_test.go:26:sqL管道出来的值:4\nchannel_test.go:26:sqL管道出来的值:6\nchannel_test.go:26:sqL管道出来的值:8\ngotooltest2json:signal:interrupt

通过无缓冲管道,可以让两个协程之间的逻辑进行先后同步执行。

我们将代码最后面一段代码修改下:

c:=0//定义一个值\nfor{//通过遍历不断从sqL通道中读取数据\nt.Log("sqL管道出来的值:",<-sqL)\nifc>5{\nclose(sqL)//遍历5次后,将sqL关闭\n}else{\nc++\n}\n}

输出:

===RUNTestChannel2\nchannel_test.go:28:sqL管道出来的值:0\nchannel_test.go:28:sqL管道出来的值:2\nchannel_test.go:28:sqL管道出来的值:4\nchannel_test.go:28:sqL管道出来的值:6\nchannel_test.go:28:sqL管道出来的值:8\nchannel_test.go:28:sqL管道出来的值:10\nchannel_test.go:28:sqL管道出来的值:12\nchannel_test.go:28:sqL管道出来的值:0\n---FAIL:TestChannel2(6.01s)\npanic:closeofclosedchannel[recovered]\n\tpanic:closeofclosedchannel

输出后抛出panic,这是由于向一个关闭的通道进行发送导致的。对于最后一个打印的值是0,这是由于通道关闭,接收端可以不阻塞地从关闭的通道中获取零值。目前没有办法直接监测一个通道是否被关闭,不过可以通过接收通道的状态来获取,在接收一个通道返回数据时,可以通过两个变量接收,第二个变量表示当前通道的状态:

v,ok:=<-numL\nif!ok{\nbreak\n}

在使用过程中尽量注意在发送端关闭通道,同时对同步接收端尽量不要无穷遍历。

由于通道最好在发送端关闭,所以我们在使用通道时,可以对函数中的通道进行标记,是接收还是发送,一般情况下一个通道在函数中只担任一种功能,发送或接收,我们可以标记出通道的功能,如果对接收的通道进行关闭操作,会编译失败

packagemain\n\nimport(\n"fmt"\n"testing"\n"time"\n)\nfuncleft(outchan<-int){//标记chan为输入\nforx:=1;x<10;x++{\nout<-x\n}\nclose(out)\n}\nfuncright(in<-chanint){//标记chan为输出\nfor{\nv:=<-in\nifv==0{\nbreak\n}\nfmt.Println("--->>>:",v)\n}\n}\nfuncTestChannel3(t*testing.T){\nl:=make(chanint)\ngoleft(l)\nright(l)//这个函数不能在并发运行,否则将会直接结束\n}

输出:

===RUNTestChannel3\n--->>>:1\n--->>>:2\n--->>>:3\n--->>>:4\n--->>>:5\n--->>>:6\n--->>>:7\n--->>>:8\n--->>>:9\n---PASS:TestChannel3(0.00s)\nPASS

使用过程中建议明确下。

带缓存的Channel内会存在一个队列,队列的容量在创建时需要指定,不带缓存channel其实是队列为0的channel。

ch1:=make(chanint,5)//定义一个可以缓存5个元素的通道

向带缓存Channel的发送操作会向其内部队列的尾部插入元素,接收操作则是从队列的头部移除元素。如果内部缓存队列是满的,那么发送操作将会阻塞直到一个接收操作而释放了新的队列空间。相反,如果channel是空的,接收操作将阻塞直到有一个发送操作而向队列插入元素。

packagemain\n\nimport(\n"fmt"\n"testing"\n"time"\n)\n\nfuncinsert(cchan<-int){\nforx:=1;x<7;x++{\ntime.Sleep(time.Second)\nfmt.Println("正在发送:",x)\nc<-x\n}\nclose(c)\n}\nfuncreadChan(c<-chanint){\nfor{\nfmt.Println("正在读取")\nv,ok:=<-c\nif!ok{\nbreak\n}\nfmt.Println("完成读取:",v)\ntime.Sleep(time.Second*5)//读取一次后,sheep5秒,gen\n}\n}\n\nfuncTestChannel4(t*testing.T){\nch1:=make(chanint,3)//定义一个可以缓存3个元素的通道\ngoinsert(ch1)\nreadChan(ch1)\n}

输出:

===RUNTestChannel4\n正在读取\n正在发送:1\n完成读取:1\n正在发送:2\n正在发送:3\n正在发送:4\n正在发送:5\n正在读取\n完成读取:2\n正在发送:6\n正在读取\n完成读取:3\n正在读取\n完成读取:4\n正在读取\n完成读取:5\n正在读取\n完成读取:6\n正在读取\n---PASS:TestChannel4(31.02s)\nPASS

从输出可以看出,在readChan函数中,第一次读取了一个,然后睡眠5秒,在这期间,insert函数里将队列充满,在readChan函数第二次读取后,insert函数又向channel发送了最后一个数据,并且关闭channel,之后每隔5秒钟,readChan都会读取其中的数据,直到读取状态为false,跳出。

接收通道信息还有一种方式可以方便的便利,上面代码中我们通过for循环类似while的方式实现,同时可以使用range来接收数据,我们改下上面的readChan函数逻辑:

funcreadChan(c<-chanint){\nforv:=rangec{\nfmt.Println("完成读取:",v)\ntime.Sleep(time.Second*5)//读取一次后,sheep5秒,gen\n}\nfmt.Println("通道已关闭!")\n}

看下输出:

===RUNTestChannel4\n正在发送:1\n完成读取:1\n正在发送:2\n正在发送:3\n正在发送:4\n正在发送:5\n完成读取:2\n正在发送:6\n完成读取:3\n完成读取:4\n完成读取:5\n完成读取:6\n通道已关闭!\n---PASS:TestChannel4(31.03s)\nPASS

range遍历通道的数据类似slice,如果发送端显式的关闭通道,则range通道自动遍历结束!

在开发程序时,需要注意,只有生产者发送端才可以关闭channel,接收者关闭可能会导致panic,另外,channel不像文件一样需要经常关闭,只有当你确认不再发送数据时,在进行关闭即可。

关于channel的内容到此结束,希望对大家有所帮助。

本站涵盖的内容、图片、视频等数据,部分未能与原作者取得联系。若涉及版权问题,请及时通知我们并提供相关证明材料,我们将及时予以删除!谢谢大家的理解与支持!

Copyright © 2023