介绍
channel
一些基础介绍这里就不过多涉及了,都1202年了,我不相信用过Go
的人没用过channel
。
当然下图也涵盖了大部分使用姿势。
有一道使用channel
进行任务编排的经典的题。题目如下:
有四个goroutine
,编号为 1、2、3、4。每秒钟会有一个 goroutine
打印自己的编号。请你实现这个程序,让输出的编号总是按照 1、2、3、4、1、2、3、4、……的顺序打印出来。就像这样,
可以自己先思考下,代码也可以通过后台回复击鼓传花
获取。
原理解析
从一个简单的例子说起。
创建一个main.go
文件,代码如下,
我们来看看这段代码编译以后长啥样。
想得到go
程序的汇编代码并不难。
可以使用go tool compile -N -l -S main.go
生成汇编代码:
或者使用go tool compile -N -l main.go
先编译出代码,然后再使用go tool objdump main.o
反汇编出代码。
还可以通过go build -gcflags -S main.go
同样可以得到汇编的代码。
上面两种我就不演示了,可以自行实验。他们之中flag
的具体含义也可以自行了解。
如果你觉得上面要自己敲代码比较麻烦,我推荐一个更加直接可视化的工具。
综上,从编译的代码我们可以看出,上述初始化一个channel
,ch
:
=
make
(
chan
struct
{})
实际上调用的是runtime.makechan
。
从函数中,我们能知道最终返回一个runtime.hchan
的指针。
runtime.hchan
结构。
我们先来解释hchan
结构体各个字段的含义,之后在案例介绍中会更加详细的说明他们的作用。
先来看qcount
和dataqsiz
有什么区别?
你去银行办事,银行有5个办事窗口,那么dataqsiz
就等于5。在这里体现的是channel
的容量为5。去银行的时候,当前有3个窗口有人正在办事,那么qcount
就等于3,体现channel
当前有3个数据元素。那么此时银行还可以再接待2个客户,对应还可以往channel
发送2个数据元素。
其他字段现在看看说明就行了,后面会细讲。
到这里我们就知道创建一个channel
本质上就是得到一个runtime.hchan
的指针,后续对此chan
的操作,无非就是对结构体字段进行相对应的操作。
同时我们也能猜出,为啥channel
能在不同的g中传递消息,而对于使用者来说不用担心并发的问题。
其实就是hchan
内部使用互斥锁来保证了并发安全。
最后我们来看一下runtime.makechan
函数核心实现,当然注释已经很明白了。
可以看到创建的时候有一段switch
分支代码,那么什么情况下会走对应的case
呢?
根据上面的信息,我们可以得出,
- 如果创建一个无缓冲
channel
,那么只需要为runtime.hchan
本身分配一段内存空间即可。 - 如果创建的缓冲
channel
存储的类型不是指针类型,会为当前channel
和存储类型元素的缓冲区,分配一块连续的内存空间。 - 在默认情况下(缓冲
channel
存储类型包含指针),会单独为runtime.hchan
和缓冲区分配内存。
总结
这篇我们主要介绍了如何获取go程序的汇编代码,通过汇编代码知道创建channel
的具体函数runtime.makechan
。
同时我们还知道不同的创建姿势会导致走向不同的内存空间分配逻辑。
最后通过创建函数我们知道channel
在程序运行时是使用runtime.hchan
来表示。
下一篇我们继续。