持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第30天,点击查看活动详情
3.4.2、直接接收
与发送类似,这里是直接获取 sendq 的队首等待的sudug 进行处理
if sg := c.sendq.dequeue(); sg != nil {
// Found a waiting sender. If buffer is size 0, receive value
// directly from sender. Otherwise, receive from head of queue
// and add sender's value to the tail of the queue (both map to
// the same buffer slot because the queue is full).
recv(c, sg, ep, func() { unlock(&c.lock) }, 3)
return true, true
}
重点在于 recv 方法,这里比相比 send 方法较为复杂
- 对于无缓冲的队列,直接使用recvDirect 将数据拷贝到目标地址
- 对于有缓冲的队列,首先将buf 中recvx 索引位置的数据拷贝到目标地址;然后将sudog中的数据拷贝到 buf 中recvx 索引位置, recvx 加一,指向下一个待接收的索引;然后执行 c.sendx = c.recvx
- 将发送数据的G 加入到runnext 队列,等待唤醒 下图展示无缓冲接收数据的流程:
func recv(c *hchan, sg *sudog, ep unsafe.Pointer, unlockf func(), skip int) {
if c.dataqsiz == 0 {
if ep != nil {
// copy data from sender
recvDirect(c.elemtype, sg, ep)
}
} else {
// Queue is full. Take the item at the
// head of the queue. Make the sender enqueue
// its item at the tail of the queue. Since the
// queue is full, those are both the same slot.
qp := chanbuf(c, c.recvx)
// copy data from queue to receiver
if ep != nil {
typedmemmove(c.elemtype, ep, qp)
}
// copy data from sender to queue
typedmemmove(c.elemtype, qp, sg.elem)
c.recvx++
if c.recvx == c.dataqsiz {
c.recvx = 0
}
c.sendx = c.recvx // c.sendx = (c.sendx+1) % c.dataqsiz
}
sg.elem = nil
gp := sg.g
unlockf()
gp.param = unsafe.Pointer(sg)
sg.success = true
if sg.releasetime != 0 {
sg.releasetime = cputicks()
}
goready(gp, skip+1)
}
这里理解一下 c.sendx = c.recvx 的含义:我们知道 sendx 代表数据 待发送的索引位置, recvx 代表 待接收的索引位置:
- buf初始化后: sendx == recvx == index 0
- 发送5条数据,接收2条数据以后:此时 队列中元素数量为 3
- 未发送数据,再次接收3条数据以后:此时队列中元素数量为0
- 再次发送5条数据:此时队列中元素数量为5,此时下一条待发送数据将放置到index = 2的位置
- 再次发送3条数据:此时这个待发送队列以满,如果再次发送,将挂载到sendq 中进行等待
- 这里已经可以得出,直接接收的过程中,对于有缓冲的队列,缓冲区必然已经存满;将index 5的数据拷贝到目标位置,将sudog中的数据拷贝到index5位置,然后recvx +1 ;sendx = recvx ,表示将sudog 中的数据加入buf 队尾 等待后期接收;