golang中的channel到底是什么-4

138 阅读1分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第29天,点击查看活动详情

3.3.3、存在缓冲区

如果当前G 无等待接收的sudog, 且缓冲区buf 未满,首先使用 chanbuf 计算出下一个可以存储数据的位置,将数据拷贝到buf 中的sendx 索引位置上,hchan 中 sendx 加一,标识下一个可以发送的位置; 4.png

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,返回成功 5.png

// 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、小结:整个发送的流程可以看下图:

6.png

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
}