Go Channel (底层实现逻辑)
通道(channel)是Go语言中提供协程间通信的独特方式,传统的多线程编程比较困难,常常需要开发者了解一些底层的细节(例如互斥锁、条件变量及内存屏障等)。而通过通道交流的方式,Go语言屏蔽了底层实现的诸多细节,使得并发编程更加简单快捷。将通道作为Go语言中的一等公民,是Go语言遵循CSP并发编程模式的结果,这种模型最重要的思想是通过通道来传递消息。同时,通道借助Go语言调度器的设计,可以高效实现通道的堵塞/唤醒,进一步实现通道多路复用的机制。
不要通过共享内存来通信,通过通信来共享内存。
——罗勃·派克
Channel读取、接收数据流程
Channel源代码分析
channel运行时数据结构
其中sendx
指向了当前buf
(通道的缓冲区)中的发送序号,若sendx
等于dataqsiz
(通道循环队列)的大小,则表示当前buf
中已经被置满,此时sendx置0,开启下一轮向buf的循环写入。其中recvx
指向了当前buf
中的接收序号,若recvx
等于dataqsiz
(通道循环队列)的大小,则表示当前buf中最后一个数据已经被取出,此时recvx置0,开启下一轮从buf的循环读取。
// 通道在运行时是一个特殊的hchan结构体
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
// 记录buf中的发送序号
sendx uint
// 记录buf中的接收序号
recvx uint
// 读取的堵塞协程队列,每个协程对应一个sudog结构,它是对协程的封装,包含了准备获取的写成中的元素指针等
// 当通道无缓冲区或者当前缓冲区没有数据则代表当前协程的sudog结构需要放入recvq链表末尾,并且当前协程陷入休眠状态,等待被唤醒重新执行
// 写入时:当有读取的协程正在等待时,直接从该协程链表中,获取第一个协程,并将元素直接复制到对应的协程中,同时唤醒被堵塞的协程
recvq waitq
// 写入的阻塞协程队列,
// 若当前通道无缓冲区或者当前缓冲区已满,则代表当前协程的sudog结构需要放入sendq链表末尾中,并且当前协程陷入休眠状态,等待被唤醒重新执行
// 读取时:当有等待写入的协程时,直接从等待的写入的协程链表中获取第一个协程,并将写入的元素直接复制到当前协程中,同时唤醒被堵塞的写入协程
sendq waitq
// 锁,并发保护
lock mutex
}
channel运行时数据结构hchan的创建
// chan 初始化函数 t 表示通道类型,size 代表通道中元素的大小。
// 当分配的大小为0时,只用在内存中分配hchan结构体大小即可
// 当通道的元素中不包含指针时,连续分配hchan结构体大小+size元素大小,当通道的元素中包含指针时,需要单独分配内存空间,
// 因为当元素中包含指针时,需要单独分配空间才能正常进行垃圾回收
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")
}
if hchanSize%maxAlign != 0 || elem.align > maxAlign {
throw("makechan: bad alignment")
}
// 计算需要为通道中元素分配的大小
mem, overflow := math.MulUintptr(elem.size, uintptr(size))
if overflow || mem > maxAlloc-hchanSize || size < 0 {
panic(plainError("makechan: size out of range"))
}
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