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 写数据”。