golang中的channel到底是什么-3

165 阅读2分钟

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

3.3 、channel 发送元素

当我们想要向 Channel 发送数据时,就需要使用 ch <- i 语句,编译器会将该语句最终转化为 runtime.chansend 的调用,如果我们在调用时将 block 参数设置成 true,那么表示当前发送操作是阻塞的

func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool {
  // ...
}

因为 runtime.chansend 函数的实现比较复杂,所以我们这里将该函数的执行过程分成以下的四个部分:

  • 基础校验,获取锁
  • 当存在等待的接收者时,通过 runtime.send 直接将数据发送给阻塞的接收者;
  • 当缓冲区存在空余空间时,将发送的数据写入 Channel 的缓冲区;
  • 当不存在缓冲区或者缓冲区已满时,等待其他 Goroutine 从 Channel 接收数据;
3.3.1、基础校验

如果Channel 未初始化,发送数据时会让出当前协程或者直接返回失败; 对非阻塞、未关闭并且 buf已满或者chan属于无缓冲的Channel 进行发送数据时,直接返回失败 基础校验完毕后首先会对当前channel 加锁,防止多个线程并发修改数据。如果 Channel 已经关闭,那么向该 Channel 发送数据时会报 “send on closed channel” 错误并中止程序

if c == nil {
   if !block {
      return false
   }
   gopark(nil, nil, waitReasonChanSendNilChan, traceEvGoStop, 2)
   throw("unreachable")
}
if !block && c.closed == 0 && full(c) {
   return false
}
lock(&c.lock)
if c.closed != 0 {
   unlock(&c.lock)
   panic(plainError("send on closed channel"))
}
3.3.2、直接发送

如果目标 Channel 没有被关闭并且已经有处于读等待的 Goroutine,那么 runtime.chansend 会从接收队列 recvq 中取出最先陷入等待的 Goroutine 并直接向它发送数据: 对于无缓冲的channel,如果存在读取的协程,必然会挂载到recvq队列上

if sg := c.recvq.dequeue(); sg != nil {
   // Found a waiting receiver. We pass the value we want to send
   // directly to the receiver, bypassing the channel buffer (if any).
   send(c, sg, ep, func() { unlock(&c.lock) }, 3)
   return true
}

3.png

对于send 方法,可以分为两个部分:

  • 调用 runtime.sendDirect 将发送的数据直接拷贝到 x = <-c 表达式中变量 x 所在的内存地址上;
  • 调用 runtime.goready 将等待接收数据的 Goroutine 标记成可运行状态 Grunnable 并把该 Goroutine 放到发送方所在的处理器的 runnext 上等待执行,该处理器在下一次调度时会立刻唤醒数据的接收方;
func send(c *hchan, sg *sudog, ep unsafe.Pointer, unlockf func(), skip int) {
   // ...
   if sg.elem != nil {
      sendDirect(c.elemtype, sg, ep)
      sg.elem = nil
   }
   gp := sg.g
   unlockf()
   gp.param = unsafe.Pointer(sg)
   sg.success = true
   if sg.releasetime != 0 {
      sg.releasetime = cputicks()
   }
   // 将接收方的 Goroutine 放到了处理器的 runnext 中,程序没有立刻执行该 Goroutine。
   goready(gp, skip+1)
}