Go channel close

101 阅读1分钟

channel是有一个主动关闭的方法,但是使用该方法需要注意,之前我们在channel写的源码里有看到,如果一个channel已经关闭的情况下还往channel中写入数据直接会panic的报错,这个会很严重,而且会杀死整个进程,实际开发的过程中需要注意。还有就是如果关闭一个未进行初始化的channel也是会panic的。

直接看源码吧:

func closechan(c *hchan) {
    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"))
    }

    if raceenabled {
       callerpc := getcallerpc()
       racewritepc(c.raceaddr(), callerpc, abi.FuncPCABIInternal(closechan))
       racerelease(c.raceaddr())
    }

    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)

    // Ready all Gs now that we've dropped the channel lock.
    for !glist.empty() {
       gp := glist.pop()
       gp.schedlink = 0
       goready(gp, 3)
    }
}
  • 关闭为初始化的channel会直接Panic
  • 获取锁
  • 重复关闭channel也会直接panic
  • 将阻塞的读协程的协程节点放入glist的队列
  • 将阻塞的写协程的协程节点放入glist的队列
  • 唤醒执行glist所有协程

这里有一个要注意的点: 如果一个 channel 的缓冲区已满,多个 goroutine 在阻塞写入中,这时关闭该 channel,那么所有阻塞的发送者在被唤醒后会 panic(因为 channel 已关闭)。接收端是否退出与 panic 无关,panic 的根本原因是“向已关闭的 channel 写数据”。