持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第29天,点击查看活动详情
3.3.3、存在缓冲区
如果当前G 无等待接收的sudog, 且缓冲区buf 未满,首先使用 chanbuf 计算出下一个可以存储数据的位置,将数据拷贝到buf 中的sendx 索引位置上,hchan 中 sendx 加一,标识下一个可以发送的位置;
if c.qcount < c.dataqsiz {
// Space is available in the channel buffer. Enqueue the element to send.
qp := chanbuf(c, c.sendx)
// ...
typedmemmove(c.elemtype, qp, ep)
c.sendx++
if c.sendx == c.dataqsiz {
c.sendx = 0
}
c.qcount++
unlock(&c.lock)
return true
}
3.3.4、阻塞试发送
如果上两个步骤均不满足,也就是说 没有找到接收方并且缓冲区已经满了, 将会获取一个sudog包裹当前协程,加入到 channel sendq 的队列尾部,然后让出当前协程 ,等待唤醒;当被其他协程唤醒后,将释放当前sudog,返回成功
// Block on the channel. Some receiver will complete our operation for us.
gp := getg()
mysg := acquireSudog()
mysg.elem = ep
mysg.g = gp
mysg.isSelect = false
mysg.c = c
gp.waiting = mysg
c.sendq.enqueue(mysg)
atomic.Store8(&gp.parkingOnChan, 1)
gopark(chanparkcommit, unsafe.Pointer(&c.lock), waitReasonChanSend, traceEvGoBlockSend, 2)
// ...
// someone woke us up.
releaseSudog(mysg)
// ...
return true
3.3.5、小结:整个发送的流程可以看下图:
3.4 、channel接收元素
当我们想要从 Channel 接收数据时,就需要使用 i <- ch 语句,编译器会将该语句最终转化为 runtime.chanrecv 的调用,如果我们在调用时将 block 参数设置成 true,那么表示当前接收操作是阻塞的:
func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool) {
// ...
}
与chansend 类似, 整个chanrecv 流程也分为四个部分:
- 基础数据校验,获取锁
- 当存在等待的发送者时,通过 runtime.recv 从阻塞的发送者或者缓冲区中获取数据;
- 当缓冲区存在数据时,从 Channel 的缓冲区中接收数据;
- 当缓冲区中不存在数据时,等待其他 Goroutine 向 Channel 发送数据;
3.4.1、基础校验
与发送类似,对于未初始化的chan 进行接收数据 会让出当前协程或者直接返回失败; 对于 非阻塞、buf为空或者无缓冲队列,直接返回接收失败 对于已关闭的队列 且 buf 为空的情况,直接返回失败
if c == nil {
if !block {
return
}
gopark(nil, nil, waitReasonChanReceiveNilChan, traceEvGoStop, 2)
throw("unreachable")
}
if !block && empty(c) {
if atomic.Load(&c.closed) == 0 {
return
}
if empty(c) {
if ep != nil {
typedmemclr(c.elemtype, ep)
}
return true, false
}
}
lock(&c.lock)
if c.closed != 0 && c.qcount == 0 {
if raceenabled {
raceacquire(c.raceaddr())
}
unlock(&c.lock)
if ep != nil {
typedmemclr(c.elemtype, ep)
}
return true, false
}