持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第27天,点击查看活动详情
3、channel的源码分析
channel 源码重点关注hchan 数据结构以及 makechan 、chansend、chanrev、closechan 几个方法
3.1、hchan
从对chan的用法中可以了解到,chan 区分是否有缓冲,runtime.hchan中的dataqsiz 表示缓冲区的数量,为0 即为无缓冲; runtime.hchan 结构体中的五个字段 qcount、dataqsiz、buf、sendx、recvx 构建底层的循环队列:
- qcount — Channel 中的元素个数;
- dataqsiz — Channel 中的循环队列的长度;
- buf — Channel 的缓冲区数据指针;
- sendx — Channel 的发送操作处理到的位置;
- recvx — Channel 的接收操作处理到的位置; 除此之外,elemsize 和 elemtype 分别表示当前 Channel 能够收发的元素类型和大小;sendq 和 recvq 存储了当前 Channel 由于缓冲区空间不足而阻塞的 Goroutine 列表,这些等待队列使用双向链表 runtime.waitq 表示,链表中所有的元素都是 runtime.sudog 结构; 接下来重点学习几个关键的方法:
3.2、channel 创建
编译器会将 ch := make(chan bool,10) 结构代码转化为 makechan,上述过程不重要,重点观察makechan的实现
func makechan(t *chantype, size int) *hchan {
elem := t.elem
// compiler checks this but be safe.
if elem.size >= 1<<16 {
throw("makechan: invalid channel element type")
}
// ...
mem, overflow := math.MulUintptr(elem.size, uintptr(size))
// ...
// Hchan does not contain pointers interesting for GC when elements stored in buf do not contain pointers.
// buf points into the same allocation, elemtype is persistent.
// SudoG's are referenced from their owning thread so they can't be collected.
// TODO(dvyukov,rlh): Rethink when collector can move allocated objects.
var c *hchan
switch {
case mem == 0:
// Queue or element size is zero.
c = (*hchan)(mallocgc(hchanSize, nil, true))
// Race detector uses this location for synchronization.
c.buf = c.raceaddr()
case elem.ptrdata == 0:
// Elements do not contain pointers.
// Allocate hchan and buf in one call.
c = (*hchan)(mallocgc(hchanSize+mem, nil, true))
c.buf = add(unsafe.Pointer(c), hchanSize)
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
}
makechan 分为几个部分
- 元素大小检测,必须小于1 << 16 也就是65536 字节
- 计算buf中内存占用 buf 数量 ✖️ buf元素大小 得出 mem
- 对于buf 中元素分类进行内存分配
- mem = 0 即 无缓冲队列或者 缓冲元素size 等于0 直接分配 hchanSize大小
- buf 中元素不含指针,将buf存储到hchan结构尾部,合并分配一次内存
- buf 中元素含有指针,分别对 hchan 以及buf 分配两次内存