本文转载自微信公众号《吴沁强的深夜食堂》,作者吴沁库里。转载本文请联系吴勤强深夜食堂公众号。我已经躺了太久了,该起床了。我宁愿让别人参与也不愿让别人参与我。之前断断续续看过几个Go模块的源码,但是没有写过,所以有些细节记不清了。打算写个系列文章重新登录。频道源码分析的文章太多了。一般人没有耐心看一篇文章的长篇大论,所以我打算单独写一篇,最后附上完整的ppt。当然这里不会涉及太多源码的细节,因为有时候,细节就是魔鬼。介绍一些渠道的基本介绍。我不会在这里讲太多。已经1202年了。我不相信用过Go的人没有用过channel。当然,下图也涵盖了大部分的使用姿势。有一个经典的问题是使用channels来进行任务调度。题目如下:有四个goroutine,编号分别为1、2、3、4,每秒钟一个goroutine都会打印自己的编号。请实现这个程序,让输出的数字总是按照1,2,3,4,1,2,3,4,...的顺序打印出来。就这样,大家可以自己想一想,代码也可以通过后台回复获取。原理分析从一个简单的例子开始。创建一个main.go文件,代码如下,看看这段代码编译后是什么样子的。得到一个go程序的汇编代码并不难。可以使用gotoolcompile-N-l-Smain.go生成汇编代码:或者先使用gotoolcompile-N-lmain.go编译代码,再使用gotoolobjdumpmain.o反汇编代码。也可以通过gobuild-gcflags-Smain.go获取编译后的代码。以上两个我就不演示了,大家可以自己实验。其中flag的具体含义也可以自行理解。如果你觉得自己敲代码很麻烦,我推荐一个更直接的可视化工具。综上所述,从编译后的代码我们可以看出,上面的一个通道的初始化实际上调用了runtime.makechan。ch:=make(chanstruct{})图片从函数中我们可以知道,最终会返回一个runtime.hchan指针。runtime.hchan结构。下面先解释一下hchan结构体各个字段的含义,然后在案例介绍中更详细地解释它们的作用。我们先看看qcount和dataqsiz的区别。去银行办理业务,银行有5个服务窗口,所以dataqsiz等于5。这里体现的是通道容量为5。去银行,目前有3个窗口人正在工作,所以qcount等于3,表示通道当前有3个数据元素。这时候银行还可以再接收到2个客户,相应的,也可以向通道发送2个数据元素。其他的字段,现在看描述就好了,后面会详细说明。至此我们知道,创建一个channel本质上就是获取一个runtime.hchan指针,后续对该channel的操作无非就是对结构体字段的相应操作。同时我们也可以猜到为什么channel可以在不同的g中传输消息,用户不用担心并发问题。实际上,hchan内部使用了互斥体来保证并发安全。最后,我们看一下runtime.makechan函数的核心实现。当然,评论很明确。可以看到创建的时候有switch分支代码,那么在什么情况下会取对应的case呢?根据以上信息,我们可以得出结论,如果创建一个unbufferedchannel,只需要为runtime.hchan本身分配一段内存空间即可。如果创建的缓冲通道的存储类型不是指针类型,则会为当前通道和存储类型元素的缓冲分配一块连续的内存空间。默认情况下(缓冲通道存储类型包括指针),runtime.hchan和缓冲区是分开分配的。总结一下这篇文章,我们主要介绍了如何获取go程序的汇编代码,并通过汇编代码知道了具体的函数runtime.makechan来创建通道。同时我们也知道,不同的创建姿势会导致不同的内存空间分配逻辑。最后,通过创建一个函数,我们知道程序运行时通道由runtime.hchan表示。我们将在下一篇文章中继续。
