GO成神之路: 详解channel(五)|Go主题月

453 阅读2分钟

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)
}

从上面这段代码中我们可以看出:

  1. c.buf存储的是所有需要被传递的数据
  2. c.recvx与c.sendx分别指向待接收的数据索引与待发送的数据索引
  3. 接收者会通过c.recvx从c.buf取数据,能取到则消费后将偏移量+1,但是此时sg中的值无法被消费,所以放入c.buf中并使sendx指向这个值得索引
  4. 接收者通过c.recvx从c.buf取不到数据时,说明队列是空得,或者已经被消费完了,那么直接消费sg中得值就行了,不然将sg中值先放入buf再取出会浪费时间。