Go源码学习:关闭channel的内部实现是什么样的?

  作为一个Gopher在刚开始学习channel的时候,一定见过以下关于关闭channel会发生什么情况的总结:

  关闭一个值为nil的channel将会panic关闭一个已经关闭的channel将会panic向一个已经关闭的channel发送数据会panic

  本节将学习关闭channel,即close(ch)的内部实现。关闭channel实际上调用的是runtime.closechan这个函数。

  

Go源码学习:关闭channel的内部实现是什么样的?

  runtime.closechan函数签名如下,它只有一个*hcan参数指向要关闭的channel,它没有返回值。

  > runtime.closechan() /usr/local/Cellar/go/1.17.2/libexec/src/runtime/chan.go:355 (hits goroutine(1):1 total:1) (PC: 0x1004b4a)

  Warning: debugging optimized function

  350: src := sg.elem

  351: typeBitsBulkBarrier(t, uintptr(dst), uintptr(src), t.size)

  352: memmove(dst, src, t.size)

  353: }

  354:

  => 355: func closechan(c *hchan) {

  356: if c == nil {

  357: panic(plainError("close of nil channel"))

  358: }

  359:

  360: lock(&c.lock)

  下图是在阅读runtime.closechan源码,梳理其内部执行流程时,绘制的活动图,只是梳理大致脉络加深理解,不一定完全准确:

  

Go源码学习:关闭channel的内部实现是什么样的?

  clonsechan函数内部首先是对异常情况的判断,如果channel是nil将直接panic,如果channel已经关闭,将直接panic。

  if c==nil {

  panic(plainError("close of nil channel"))

  }

  lock(&c.lock)

  if c.closed !=0 {

  unlock(&c.lock)

  panic(plainError("close of closed channel"))

  }

  到这里进入关闭channel的主逻辑:

  首先将channel的底层数据结构hchan上的closed字段设置成1,表示当前channel已经关闭了。接下来,会初始化一个gList结构,用来存放recvq和sendq两个等待队列中的接收方和发送方的Goroutine。将接收方等待队列recvq上的sudog出队,清理未被处理的元素,并将接收方的G都添加到gList中将发送方等待队列sendq上的sudog出队,清理未被处理的元素,并将发送方的G都添加到gList中最后执行解锁操作

  c.closed=1

  var glist gList

  // release all readers

  for {

  sg :=c.recvq.dequeue()

  if sg==nil {

  break

  }

  if sg.elem !=nil {

  typedmemclr(c.elemtype, sg.elem)

  sg.elem=nil

  }

  if sg.releasetime !=0 {

  sg.releasetime=cputicks()

  }

  gp :=sg.g

  gp.param=unsafe.Pointer(sg)

  sg.success=false

  if raceenabled {

  raceacquireg(gp, c.raceaddr())

  }

  glist.push(gp)

  }

  // release all writers (they will panic)

  for {

  sg :=c.sendq.dequeue()

  if sg==nil {

  break

  }

  sg.elem=nil

  if sg.releasetime !=0 {

  sg.releasetime=cputicks()

  }

  gp :=sg.g

  gp.param=unsafe.Pointer(sg)

  sg.success=false

  if raceenabled {

  raceacquireg(gp, c.raceaddr())

  }

  glist.push(gp)

  }

  unlock(&c.lock)

  执行到这一步,原来在两个等待队列recvq和sendq中阻塞的Goroutine都被放到了glist中,最后为这些被阻塞的G调用goready函数唤醒这些G,将重新对这些G进行调度。

  // Ready all Gs now that we've dropped the channel lock.

  for !glist.empty() {

  gp :=glist.pop()

  gp.schedlink=0

  goready(gp, 3)

  }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值