本文基于go1.14.12版本,channel是go语言中的一大特色,它的用途是用来在goroutine之间传输数据,这里你可能要问了,为什么一定得是goroutine之间传输数据呢,函数之间传递不行吗?
因为正常的传输数据直接以参数的形式传递就可以了,只有在并发场景当中,多个线程彼此隔离的情况下,才需要一个特殊的结构传输数据。
废话不多说,channel的数据结构是:
定义个一个channel的时候,会生成一个hchan的数据结构:
qcount:表示当前buf中有多少个数据
dataqsize:也就是buffer size,最终会体现到buf的大小里
buf:起始就是一个环形数组
sendx:环形数组索引字段,发送方往channel里填入数据的位置,当buf满了之后,又回到0
recvx:环形数组索引字段,接收方从channel里取数据的位置,当buf满了之后,又回到0
sendq:如果buf满了,然后又有更多的发送方发送过来数据,就会把发送方的* goroutine先挂在sendq的队列上阻塞起来
recvq:如果buf空了,而且也没有阻塞的sendq,那么就会把接收方的goroutine挂在recvq上阻塞起来
lock:保护channel所有字段的锁
当我们往channel里面写数据的时候,channel里面这个小社会是咋这个运行的的?
当我们定义一个没有缓冲区的channel的时候:buffer size = 0
当我们定义一个有缓冲区的channel的时候:buffer size=3
当qcount == dataqsize 也就是说 buf已经满了,那么发送方的goroutine就需要阻塞等待。就会把发送方的goroutine的运行情况以及现场也就是上下文,将其保存起来,生成一个sudog的结构,挂在sendq队列上。
当接收方取数据的时候,发现buf是空的,没有数据可以取出来,那么就会把接收方的goroutine和其上下文保存起来,生成一个sudog结构,挂在recvq队列上。
我们看到recvx取数据的索引位置是0
那么我们就从第0个位置取数据
取出数据之后,qcount即buf的数量-1,recvx即接收方索引位置+1
而后,buf里面已经空出了位置,qcount=2小于dataqsize,这时候我们要把sendq里面阻塞的sudog拿出来,开始往buf里面写数据,我们看到sendx是0,那么我们就要从第0个位置开始插入数据。
将sendq中的6放入buf的0位置
将sendq中的6放入buf的0位置后,buf的数量变成了3个,qcount+1变成了3,而sendx也从0+1变成了1
总结:
channel send:
channel recv:
gopark() 挂起 goready()唤醒
可接管的阻塞,均是由gopark() 挂起,每一个gopark() 都会对应一个唤醒方。