chan 底层是一个环形缓冲数组
type hchan struct {
qcount uint // 元素数量
dataqsiz uint // 容量
buf unsafe.Pointer // 指向环形缓冲数组的指针
elemsize uint16
closed uint32 // 是否关闭
elemtype *_type // chan 中元素的类型
sendx uint // 下次写的索引
recvx uint // 下次读的索引
recvq waitq // 读等待队列(chan 为空时,阻塞读 chan 的 goroutine)
sendq waitq // 写等待队列(chan 已满时,阻塞写 chan 的 goroutine)
lock mutex // chan 的互斥锁
}
type waitq struct {
first *sudog // 队头指针
last *sudog // 队尾指针
}
type sudog struct {
g *g // 绑定的 goroutine
next *sudog
prev *sudog
// 等待写入 chan 的数据(写等待 goroutine)
// 等待从 chan 中读出的数据(读等待 goroutine)
elem unsafe.Pointer
isSelect bool // 是否因 select 而被阻塞
// true:因 chan 通信而被唤醒
// false:因 chan 关闭而被唤醒
success bool
c *hchan // 绑定的 chan
}
写 chan
chan 为 nil
当前 goroutine 被永久性阻塞(如果是 main goroutine,fatal error 程序退出)
chan 已关闭
panic
读等待队列非空
- 拿锁
- 从读等待队列 recvq 中取出队头的 sudog
- 将数据直接写入 sudog.elem
- 释放锁
- 唤醒 sudog 对应的读等待 goroutine
读等待队列为空,buf 未满
- 拿锁
- 将数据写入 sendx 处
- sendx++,qcount++
- 释放锁
读等待队列为空,buf 已满
- 获取一个 sudog,绑定对应的 goroutine 和 chan
- 将 sudog 放入写等待队列 sendq
- 挂起当前 goroutine
读 chan
chan 为 nil
当前 goroutine 被永久性阻塞(如果是 main goroutine,fatal error 程序退出)
chan 已关闭
可以继续读取,若 buf 为空,会得到 chan 对应类型的零值
写等待队列非空
- 拿锁
- 从写等待队列 sendq 中取出队头的 sudog
- 如果环形缓冲数组容量为 0,直接读取 sudog.elem 中的数据;否则,说明此时环形缓冲数组已满,读取 recvx 处的数据,并将 sudog.elem 中的数据直接写入 recvx 处,recvx++,sendx = recvx
- 释放锁
- 唤醒 sudog 对应的写等待 goroutine
写等待队列为空,buf 非空
- 拿锁
- 读取 recvx 处的数据
- recvx++,qcount–
- 释放锁
写等待队列为空,buf 为空
- 获取一个 sudog,绑定对应的 goroutine 和 chan
- 将 sudog 放入读等待队列 recvq
- 挂起当前 goroutine
关闭 chan
- chan 为 nil 或 chan 已关闭 -> panic
- 关闭 chan
- 唤醒所有被该 chan 阻塞的 goroutine
select
- 非阻塞型(with default)
- 阻塞型(without default)
随机执行一个 case,如果所有的 case 都无法完成,有 default 则走 default,没有 default,则将当前 goroutine 加入到所有 case 对应 chan 的等待队列中,并挂起当前 goroutine
如果当前 goroutine 被某个 case 上的 chan 唤醒,还要将当前 goroutine 从所有 case 对应 chan 的等待队列中移除


