golang中的channel到底是什么-5

156 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 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 队列,等待唤醒 下图展示无缓冲接收数据的流程: 7.png
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 8.png
  • 发送5条数据,接收2条数据以后:此时 队列中元素数量为 3 9.png
  • 未发送数据,再次接收3条数据以后:此时队列中元素数量为0 10.png
  • 再次发送5条数据:此时队列中元素数量为5,此时下一条待发送数据将放置到index = 2的位置 11.png
  • 再次发送3条数据:此时这个待发送队列以满,如果再次发送,将挂载到sendq 中进行等待 12.png
  • 这里已经可以得出,直接接收的过程中,对于有缓冲的队列,缓冲区必然已经存满;将index 5的数据拷贝到目标位置,将sudog中的数据拷贝到index5位置,然后recvx +1 ;sendx = recvx ,表示将sudog 中的数据加入buf 队尾 等待后期接收; 13.png