只有发送方能关闭 channel 且只能关一次;关闭语义是“不再发送数据”,接收方无权关闭,否则破坏并发安全;关闭后缓存数据仍可读取;判断是否关闭应使用 value, ok :=

只有发送方能关闭 channel,且只能关一次;接收方读取已关闭的 channel 是安全的,但向已关闭的 channel 发送数据会直接触发 panic。
为什么 必须由发送方关闭?
关闭 channel 的语义是“我不会再发数据了”,这是发送方的职责边界。接收方无法预知发送是否结束,若擅自关闭,其他发送协程可能仍在尝试写入,立刻 panic。
- 多个接收者共用一个
channel时,关闭权仍归唯一发送方所有 - 多个发送方共存时,不能靠某一方“猜”着关——必须协调(如用
sync.Once或额外信号donechannel) - 关闭后,
close(ch)不会清空缓冲区;剩余缓存数据仍可被正常读出
怎么判断 channel 已关闭?别用 IsClosed 函数
网上流传的通过 select + default 判断是否关闭的 IsClosed 函数有严重副作用:它会真实地从 channel 中偷读一个值,破坏状态,且结果瞬时失效,完全不可靠。
- 正确方式只有两种:
value, ok := 中的ok == false,或for range ch自动退出 -
for range更简洁、不易出错,适用于单接收者消费全部数据的场景 - 需要在
select中处理关闭时,必须配合ok检查,例如:case v, ok :=
多协程并发写入时如何防重复关闭?
当多个 goroutine 都可能成为“最后一个发送者”并试图关闭 channel 时,close(ch) 被调用两次会 panic。不能靠加锁判断再关,而应使用 sync.Once 做幂等封装。
立即学习“go 语言免费学习笔记(深入)”;
var once sync.Once safeClose := func(ch chan int) {once.Do(func() {close(ch) }) } // 下面任意次数调用都安全 go safeClose(dataCh) go safeClose(dataCh)
- 不要用
defer-recover来兜底关闭 —— 这是掩盖设计缺陷,不是优雅 - 若发送逻辑本身依赖外部条件(如超时、错误),建议用额外的
donechannel 通知所有发送者停止,再由主控协程统一关闭 - 关闭后,记得同步等待接收方完成(如用
sync.WaitGroup),否则主程序可能提前退出,丢失未处理的数据
最容易被忽略的一点:关闭不是“清空”操作,也不等于“立即终止所有 goroutine”。它只是发一个信号;真正安全退出,取决于你是否让每个接收者都完成了自己的读取循环,以及是否确保没有 goroutine 还在往该 channel 写入。