recv源码分析
func recv(c *hchan, sg *sudog, ep unsafe.Pointer, unlockf func(), skip int) {
// dataqsiz==0说明没有数据积压,则将sg中的保存的待发送的值复制到ep中,ep就是<-左边的变量
if c.dataqsiz == 0 {
// ...
// ep是接收者
if ep != nil {
// 将sg的elem复制到ep中
recvDirect(c.elemtype, sg, ep)
}
} else {
// 返回接收者应该从c.buf读取的值的内存地址
qp := chanbuf(c, c.recvx)
//...
// 存在接收者时
if ep != nil {
// 将待接收的qp中的值,复制到ep中
typedmemmove(c.elemtype, ep, qp)
}
// 因为ep从c.buf中取了一个值,此时没有办法消费sg中的值,则会将sg中值放入c.buf中,也就是qp指向的内存地址
typedmemmove(c.elemtype, qp, sg.elem)
// 消费者索引+1
c.recvx++
if c.recvx == c.dataqsiz {
c.recvx = 0
}
// 因为qp就是c.recvx的索引对应的内存地址,而qp没有被消费也就是没有被发送出去,所以待发送者的索引就是c.recvx
c.sendx = c.recvx // c.sendx = (c.sendx+1) % c.dataqsiz
}
// 下面将设置sg为success说明发送者已经完成了
sg.elem = nil
gp := sg.g
unlockf()
gp.param = unsafe.Pointer(sg)
sg.success = true
if sg.releasetime != 0 {
sg.releasetime = cputicks()
}
// 准备协程,并将协程推送到协程队列中,协程队列由协程管理器自行调度,这也是为什么channel不是顺序的原因将
goready(gp, skip+1)
}
从上面这段代码中我们可以看出:
- c.buf存储的是所有需要被传递的数据
- c.recvx与c.sendx分别指向待接收的数据索引与待发送的数据索引
- 接收者会通过c.recvx从c.buf取数据,能取到则消费后将偏移量+1,但是此时sg中的值无法被消费,所以放入c.buf中并使sendx指向这个值得索引
- 接收者通过c.recvx从c.buf取不到数据时,说明队列是空得,或者已经被消费完了,那么直接消费sg中得值就行了,不然将sg中值先放入buf再取出会浪费时间。