文章目录
Channel概述
Channel通过通信的方式在goroutine之间共享内存,是支撑Go语言高性能并发编程模型的重要结构,本文将分析Channel相关的创建、发送、接收、关闭函数的源代码。
ps:源代码只给出了重要逻辑核心部分
Channel结构体信息
Channel结构体为hchan
,源码如下:
type hchan struct {
qcount uint // 元素个数
dataqsiz uint // 循环队列的长度
buf unsafe.Pointer // 缓冲区数据指针
elemsize uint16 //元素类型大小
closed uint32 //是否关闭标志位
elemtype *_type // 元素类型
sendx uint // 发送数据的发送偏移位置(缓冲区中)
recvx uint // 接收数据的接收偏移位置(缓冲区中)
recvq waitq // 阻塞的发送Goroutine列表,链表形式,后来的协程在链表尾部
sendq waitq // 阻塞的接收Goroutine列表
lock mutex //锁
}
Channel创建
创建chan的函数为makechan
,判断是否创建缓冲区。
func makechan(t *chantype, size int) *hchan {
//一些元素检查部分,略过
elem := t.elem
mem, overflow := math.MulUintptr(elem.size, uintptr(size))
var c *hchan
switch {
case mem == 0: //无缓冲区
c = (*hchan)(mallocgc(hchanSize, nil, true))
c.buf = c.raceaddr()
case elem.ptrdata == 0: //类型不是指针类型, 分配一块连续的内存空间
c = (*hchan)(mallocgc(hchanSize+mem, nil, true))
c.buf = add(unsafe.Pointer(c), hchanSize)
default: //默认分配hchan和缓冲区空间
c = new(hchan)
c.buf = mallocgc(mem, elem, true)
}
//初始化信息
c.elemsize = uint16(elem.size)
c.elemtype = elem
c.dataqsiz = uint(size)
lockInit(&c.lock, lockRankHchan)
}
return c
}
chansend发送
发送函数为chansend,主体分为3个部分:
- 如果存在接收方,直接发送
- 缓冲空间有剩余时,写入缓冲空间
- 上述都不满足时,阻塞发送,等待其他Goroutine接收操作
func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool {
//一些元素检查部分,略过
lock(&c.lock)
if c.closed != 0 {
//如果发送时管道已经关闭,直接panic
unlock(&c.lock)
panic(plainError("send on closed channel"))
}
//第一个判断:如果接收队列中有协程在等待,那么直接发送数据
//dequeue()取链表头部元素,也就是最先陷入等待的Goroutine
if sg := c.recvq.dequeue(); sg != nil {
send(c, sg, ep, func() {
unlock(&c.lock) }, 3) //send函数见下面分析
//
return true
}
//第二个判断:缓冲区如果有剩余,则将数据写入缓冲区(无缓冲区channel跳过)
if c.qcount < c.dataqsiz {
qp :