Golang并发控制 - Channel
CSP并发编程
CSP(Communicating Sequential Processes,通信顺序进程):是用户描述并发系统中交互模式的形式化语言,其通过通道传递消息。核心思想是:不要通过共享内存来通信,通过通信来共享内存
通道结构与环形队列
通道在运行时是一个特殊的hchan结构体: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 protects all fields in hchan, as well as several
// fields in sudogs blocked on this channel.
//
// Do not change another G's status while holding this lock
// (in particular, do not ready a G), as this can deadlock
// with stack shrinking.
lock mutex
}
每个字段的含义:

对于有缓冲的通道,存储在buf中的数据虽然是线性的数组,但是用数组和序号recvx, recvq模拟了一个环形队列:
- recvx:可以找到从buf哪个位置获取通道中的元素
- sendx:能够找到写入时放入buf的位置,这样做主要是为了重用已使用过的空间。
- recvx到sendx的距离,代表通道队列中元素的数量

通道操作
初始化
在运行时调用了makechan函数 makechan(t *chantype, size int) *hchan
- 当size为0,只在内存中分配hchan结构体的大小
- 当通道元素不包含指针,连续分配hchan结构体大小 + size大小
- 当通道元素包含指针,需要单独分配内存空间
写入
当向通道写入数据,调用了chansend
,写入元素时分3种不同的情况
有正在等待的读取协程
recvq字段存储了正在等待的协程链表,每个协程对应一个sudog结构,它是对协程的封装。当有读取的协程正在等待时,直接从等待的读取协程链表中获取第一个协程,并将元素之间复制到对应的协程中,再唤醒该协程。
缓冲区有空余
向当前缓冲区写入当前元素
缓冲区无空余
将当前协程的sudog结构体放入sendq链表末尾,并且当前协程陷入休眠状态,等待被唤醒重新执行
读取
调用了chanrecv
函数,也分3中不同的情况
有正在等待的写入协程
直接从等待的写入协程链表获取第一个协程,并将写入的元素直接复制到当前协程中,再唤醒被阻塞的写入协程,这样当前协程不需要陷入休眠。
缓冲区有元素
读取缓冲区的数据,写入当前读取的协程中
缓冲区无数据
将当前协程的sudog结构体放入recvq链表末尾,当前协程陷入休眠状态,等待被唤醒重新执行
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 喵了个咪!