上一次,研究了一下map的实现原理,原文链接:golang中map的实现原理_初级程序猿一步步进阶高级的博客-优快云博客
这次我们来研究一下,channel的实现原理。
1、初始化一个channel变量
ch := make(chan int, 10)
2、make后,也会调用对应的方法
当make初始化chan类型的数据时,会调用makechan64()方法,在这个方法中会再调用makechan()方法,返回了一个hchan这个数据类型的地址。*注意,重点来了,chan类型的核心就是hchan这个结构体,后面我们会看hchan里有什么,以及它的核心逻辑是什么。
func makechan64(t *chantype, size int64) *hchan {
if int64(int(size)) != size {
panic(plainError("makechan: size out of range"))
}
return makechan(t, int(size))
}
func makechan(t *chantype, size int) *hchan {
// 为了方便理解省略部分代码,感兴趣可以查看这部分源码,在这里主要想展现的是makechan方法返回的其实是hchan这个结构体的地址
var c *hchan // 关键代码
switch {
default:
// Elements contain pointers.
c = new(hchan) //关键代码
c.buf = mallocgc(mem, elem, true)
}
c.elemsize = uint16(elem.size)
c.elemtype = elem
c.dataqsiz = uint(size)
return c
}
3、hchan结构体的介绍
截图来自源码文件:runtime/chan.go文件,感兴趣的可以亲自去看读一下。
qcount:已经存储了多少元素
buf:缓冲区的地址,比如 ch <- 5,会把5存在这个缓冲区
dataqsiz:最多存储多少元素
elemsize:每个元素占多少空间
closed: 表示chan是否关闭
sendx:表示当前写入的位置
recvq:表示读的位置
sendq:写满了以后会阻塞,这时会存放在队列,以待被唤醒执行
recvq:读队列与上相同逻辑,没有数据时也会阻塞等待被唤醒执行
lock:这个是非常重要的,channel具有锁机制,所以channel是安全的。
4、hchan是如何工作的?
当我们使用make初始化后,就指定了缓冲区可以存储多少个数据。比如3个
ch := make(chan int, 3)
这时候sendx和recvq都指在这个缓冲区 的0的位置,像这样
现在给ch增加两个数据,
ch <- 1
ch <- 2
就会变成这样,sendx指向位置往后移动,sendx指向的位置就是下次写入数据的位置。
如果我再写入两个数据3和4,这时候sendx走到头之后又会再从0开始,但是很不巧0的位置已经有数据了,所以只有3被写入了,4就会阻塞,阻塞的情况就会被记录到写的队列-sendq。
ch <- 3
ch <- 4
再来看看读,如果我们读了2个数据,recvq就会往后移动两个位置,同时因为读把0的位置空出来了,写的队列会被激活,把4放入0的位置上。
<- ch
<- ch
读到最后,也会重新从0开始读,它们是在这个缓冲区循环往复的读和写,再配合着两个队列和锁保证它的可靠性。
以上就是chan类型的基本运行原理了。