今天看啥  ›  专栏  ›  LitC

Channel(三)channel读

LitC  · 简书  ·  · 2021-03-18 22:25
image.jpeg

流程:

  • 消费者读取数据,首先判断生产者阻塞队列sendq是否为空,若不为空,说明要么缓冲区满了,要么没有缓冲区,导致生产者阻塞在队列中。
  • 判断是否有缓冲区,无缓冲区:从生产者阻塞队列sendq中取出一个G,从G中读取数据,唤醒G。有缓冲区:从buf队首取一个元素,从sendq取出一个G,把G中的数据写入buf队列,唤醒G。
  • 生产者队列sendq为空,判断环形队列中是否有元素,有元素:从队列中取一个。无元素:将当前goroutine加入recv,等待被唤醒。
i  <- channel
// entry points for <- c from compiled code
//go:nosplit
func chanrecv1(c *hchan, elem unsafe.Pointer) {
   chanrecv(c, elem, true)
}
func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool) {
   // raceenabled: don't need to check ep, as it is always on the stack
   // or is new memory allocated by reflect.

   if debugChan {
      print("chanrecv: chan=", c, "\n")
   }

   if c == nil {
      if !block {
         return
      }
      gopark(nil, nil, waitReasonChanReceiveNilChan, traceEvGoStop, 2)
      throw("unreachable")
   }

   // Fast path: check for failed non-blocking operation without acquiring the lock.
   if !block && empty(c) {
      // After observing that the channel is not ready for receiving, we observe whether the
      // channel is closed.
      //
      // Reordering of these checks could lead to incorrect behavior when racing with a close.
      // For example, if the channel was open and not empty, was closed, and then drained,
      // reordered reads could incorrectly indicate "open and empty". To prevent reordering,
      // we use atomic loads for both checks, and rely on emptying and closing to happen in
      // separate critical sections under the same lock.  This assumption fails when closing
      // an unbuffered channel with a blocked send, but that is an error condition anyway.
      if atomic.Load(&c.closed) == 0 {
         // Because a channel cannot be reopened, the later observation of the channel
         // being not closed implies that it was also not closed at the moment of the
         // first observation. We behave as if we observed the channel at that moment
         // and report that the receive cannot proceed.
         return
      }
      // The channel is irreversibly closed. Re-check whether the channel has any pending data
      // to receive, which could have arrived between the empty and closed checks above.
      // Sequential consistency is also required here, when racing with such a send.
      if empty(c) {
         // The channel is irreversibly closed and empty.
         if raceenabled {
            raceacquire(c.raceaddr())
         }
         if ep != nil {
            typedmemclr(c.elemtype, ep)
         }
         return true, false
      }
   }

   var t0 int64
   if blockprofilerate > 0 {
      t0 = cputicks()
   }

   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
   }

   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
   }

   if c.qcount > 0 {
      // Receive directly from queue
      qp := chanbuf(c, c.recvx)
      if raceenabled {
         raceacquire(qp)
         racerelease(qp)
      }
      if ep != nil {
         typedmemmove(c.elemtype, ep, qp)
      }
      typedmemclr(c.elemtype, qp)
      c.recvx++
      if c.recvx == c.dataqsiz {
         c.recvx = 0
      }
      c.qcount--
      unlock(&c.lock)
      return true, true
   }

   if !block {
      unlock(&c.lock)
      return false, false
   }

   // no sender available: block on this channel.
   gp := getg()
   mysg := acquireSudog()
   mysg.releasetime = 0
   if t0 != 0 {
      mysg.releasetime = -1
   }
   // No stack splits between assigning elem and enqueuing mysg
   // on gp.waiting where copystack can find it.
   mysg.elem = ep
   mysg.waitlink = nil
   gp.waiting = mysg
   mysg.g = gp
   mysg.isSelect = false
   mysg.c = c
   gp.param = nil
   c.recvq.enqueue(mysg)
   // Signal to anyone trying to shrink our stack that we're about
   // to park on a channel. The window between when this G's status
   // changes and when we set gp.activeStackChans is not safe for
   // stack shrinking.
   atomic.Store8(&gp.parkingOnChan, 1)
   gopark(chanparkcommit, unsafe.Pointer(&c.lock), waitReasonChanReceive, traceEvGoBlockRecv, 2)

   // someone woke us up
   if mysg != gp.waiting {
      throw("G waiting list is corrupted")
   }
   gp.waiting = nil
   gp.activeStackChans = false
   if mysg.releasetime > 0 {
      blockevent(mysg.releasetime-t0, 2)
   }
   closed := gp.param == nil
   gp.param = nil
   mysg.c = nil
   releaseSudog(mysg)
   return true, !closed
}

接收操作的特性:

  • 从nil channel中接收数据会永久阻塞,而且不会被select语句选中
  • 如果channel未关闭,没有待发送者或者缓冲channel的缓冲区为空的话,不会被select语句选中
  • 从已关闭并且缓冲区为空的channel中接收数据的话,会把接收值置为空值,而且可以被select选中
  • 如果待发送队列不为空,说明无缓冲或者缓冲已满,对于无缓冲直接从待发送者复制数据到接收者,如果缓冲区已满,那么先将缓冲区中数据复制给接收者,然后将待发送者的数据复制到缓冲区并唤醒发送者
  • 只要缓冲区不为空,即使channel已关闭,依然可以从channel中获取到数据
  • 如果缓冲为空并且没有待发送者,不会被select语句选中,如果是阻塞接收操作的话,会被阻塞直到channel被关闭或者被发送者唤醒
  • 接收者被关闭操作唤醒,那么接收值会被指为空值。
    接收操作被select语句选中的情况:
  • channel已关闭
  • 缓冲区有数据
  • 待发送队列不为空



原文地址:访问原文地址
快照地址: 访问文章快照