channel是Golang中一个非常重要的特性,也是Golang CSP并发模型的一个重要体现。简单来说就是,goroutine之间可以通过channel进行通信。

channel在Golang如此重要,在代码中使用频率非常高,以至于不得不好奇其内部实现。本文将基于go 1.13的源码,分析channel的内部实现原理。

channel的基本使用

在正式分析channel的实现之前,我们先看下channel的最基本用法,代码如下:

make(chan int)c <- 1x := <- c
c <- 1x := <- c

此外,channel还分为有缓存channel和无缓存channel。上述代码中,我们使用的是无缓冲的channel。对于无缓冲的channel,如果当前没有其他goroutine正在接收channel数据,则发送方会阻塞在发送语句处。

make(chan int, 2)

channel对应的底层实现函数

<-<-
go tool compile -N -l -S hello.go
Compiler Explorer
make(chan int)runtime.makechanc <- 1runtime.chansend1x := <- cruntime.chanrecv1
runtime/chan.go

channel的构造

make(chan int)runtime.makechan
t *chantypesize int*hchan

hchan中的所有属性大致可以分为三类:

1. buffer相关的属性。例如buf、dataqsiz、qcount等。 当channel的缓冲区大小不为0时,buffer中存放了待接收的数据。使用ring buffer实现。

2. waitq相关的属性,可以理解为是一个FIFO的标准队列。其中recvq中是正在等待接收数据的goroutine,sendq中是等待发送数据的goroutine。waitq使用双向链表实现。

3. 其他属性,例如lock、elemtype、closed等。

makechanbufferhchan
bufferwaitqhchan

向channel中发送数据

c <- 1runtime.chansend
recvqrecvq
x := <- csendqsudog
recvq

send函数的实现主要包含两点:

memmove(dst, src, t.size)
goready(gp, skip+1)
recvq
dataqsiz
c.qcount < c.dataqsizsendq
goparkgoreadygoparkgoready
gopark
c <- 1c.lock

简单来说,整个流程如下:

1. 检查recvq是否为空,如果不为空,则从recvq头部取一个goroutine,将数据发送过去,并唤醒对应的goroutine即可。

2. 如果recvq为空,则将数据放入到buffer中。

sudogsendq

从channel中接收数据的过程基本与发送过程类似,此处不再赘述了。具体接收过程涉及到的buffer的相关操作,会在后面进行详细的讲解。

runtime.mutexruntime.mutex

channel的ring buffer实现

channel中使用了ring buffer(环形缓冲区)来缓存写入的数据。ring buffer有很多好处,而且非常适合用来实现FIFO式的固定长度队列。

在channel中,ring buffer的实现如下:

hchanrecvxsendxsendxrecvxrecvxsendx
buf[recvx]buf[sendx] = x

buffer的写入

当buffer未满时,将数据放入到buffer中的操作如下:

chanbuf(c, c.sendx)c.buf[c.sendx]sendx
sendxsendx

buffer的读取

sendqchanrecvrecvx
sendq
epx := <- cxsgsudog
typedmemmove(c.elemtype, ep, qp)
typedmemmove(c.elemtype, qp, sg.elem)recv++

简单来说,这里channel将buffer中队首的数据拷贝给了对应的接收变量,同时将sendq中的元素拷贝到了队尾,这样可以才可以做到数据的FIFO(先入先出)。

c.sendx = c.recvxc.sendx = (c.sendx+1) % c.dataqsizsendx == recvx

总结

channel作为golang中最常用设施,了解其源码可以帮助我们更好的理解和使用。同时也不会过于迷信和依赖channel的性能,channel就目前的设计来说也还有更多的优化空间。

参考