go基础-chan原理
chan结构(src/runtime/chan.go)
type hchan struct {
qcount uint // total data in the queue
dataqsiz uint // size of the circular queue // 这好像是个拼写错误
buf unsafe.Pointer // points to an array of dataqsiz elements
elemsize uint16
closed uint32
elemtype *_type // element type
sendx uint // send index
recvx uint // receive index
recvq waitq // list of recv waiters
sendq waitq // list of send waiters
lock mutex
}
- chan的数据缓冲区是一个环形队列,队列长度即为创建时指定的长度;对于环形队列,需要记录下一条数据的写入索引(sendx),以及下一次读取数据的索引(recvx)
- chan无数据时,读取的goroutine会堵塞,等待写入数据激活;chan数据已满时,写入的goroutine同样会堵塞,等待读取数据激活;chan的等待列表是一个链表(recvq,sendq),由于需要从头部唤醒,并且在尾部增加新的等待goroutine,所以使用的是双向链表
type waitq struct {
first *sudog
last *sudog
}
chan读写
1. 写数据(chansend)(src/runtime/chan.go)
- 判断chan是否为nil,是则panic(unreachable)
- 判断chan是否已经准备好接受数据,否则返回false
- hchan.lock加锁
- 判断chan是否已close,是则panic(send on closed channel)
- 判断是否存在等待读取goroutine,是则直接将数据写入,并唤醒读取goroutine
- 缓冲区可用,将数据写入缓冲区,并更新循环队列写入索引及数据大小
- 缓冲区已满时,将当前goroutine加入sendq,等待唤醒

2. 读数据(chanrecv)(src/runtime/chan.go)
- 判断chan是否为nil,是则panic(unreachable)
- 判断chan是否已经准备好读取数据,否则返回false
- hchan.lock加锁
- 判断chan是否已close,且数据缓冲区为空,是则返回false
- 判断是否存在等待写入goroutine,存在时:
- 无缓冲chan:直接读取数据,并唤醒写入goroutine
- 带缓冲chan:从缓冲区头部取出一个元素,从sendq中取出一个goroutine,写入数据到缓冲区尾部,唤醒写入goroutine
- 判断缓冲区是否为空,否则从缓冲区头部取出一个元素
- 缓冲区为空时,将当前goroutine加入recvq,等待唤醒

全部评论