废话不多说,直奔主题。
channel的整体结构图
简单说明:
bufsendxrecvxbuflockrecvqsendq
/runtime/chan.gohchan
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
}
复制代码
hchan
先从创建开始
我们首先创建一个channel。
ch := make(chan int, 3)
复制代码
hchan
channel中发送send(ch <- xxx)和recv(<- ch)接收
mutex
channel中队列是如何实现的
channel中有个缓存buf,是用来缓存数据的(假如实例化了带缓存的channel的话)队列。我们先来看看是如何实现“队列”的。 还是刚才创建的那个channel
ch := make(chan int, 3)
复制代码
send (ch <- xx)recv ( <-ch)hchan
send (ch <- xx)
ch <- 1
复制代码
二
ch <- 1
复制代码
三
ch <- 1
复制代码
这时候满了,队列塞不进去了 动态图表示为:
recv ( <-ch)
recv (<-ch)
<-ch
复制代码
二
<-ch
复制代码
三
<-ch
复制代码
图为:
bufrecvxsendxrecvxsendxbufsendrecvsendrecvxsendrecvx
缓存中按链表顺序存放,取数据的时候按链表顺序读取,符合FIFO的原则。
send/recv的细化操作
注意:缓存链表中以上每一步的操作,都是需要加锁操作的!
每一步的操作的细节可以细化为:
- 第一,加锁
- 第二,把数据从goroutine中copy到“队列”中(或者从队列中copy到goroutine中)。
- 第三,释放锁
每一步的操作总结为动态图为:(发送过程)
或者为:(接收过程)
Do not communicate by sharing memory; instead, share memory by communicating.channel
当channel缓存满了之后会发生什么?这其中的原理是怎样的?
使用的时候,我们都知道,当channel缓存满了,或者没有缓存的时候,我们继续send(ch <- xxx)或者recv(<- ch)会阻塞当前goroutine,但是,是如何实现的呢?
user-space threads
send (ch <- xx)recv ( <-ch)
//goroutine1 中,记做G1
ch := make(chan int, 3)
ch <- 1
ch <- 1
ch <- 1
复制代码
这个时候G1正在正常运行,当再次进行send操作(ch<-1)的时候,会主动调用Go的调度器,让G1等待,并从让出M,让其他G去使用
sudogsendq
那么,G1什么时候被唤醒呢?这个时候G2隆重登场。
p := <-ch
G2从缓存队列中取出数据,channel会将等待队列中的G1推出,将G1当时send的数据推到缓存中,然后调用Go的scheduler,唤醒G1,并把G1放到可运行的Goroutine队列中。
假如是先进行执行recv操作的G2会怎么样?
你可能会顺着以上的思路反推。首先:
sudogrecvq
ch <- 1
G1并没有锁住channel,然后将数据放到缓存中,而是直接把数据从G1直接copy到了G2的栈中。 这种方式非常的赞!在唤醒过程中,G2无需再获得channel的锁,然后从缓存中取数据。减少了内存的copy,提高了效率。
之后的事情显而易见:
互联网技术窝
参考文献: