唯一被语言规范保证的检测 channel 是否已关闭的方式是执行一次接收操作并检查第二个返回值:v, ok :=

怎么判断 channel 是否已关闭
Go 没有 isClosed() 函数,len(ch) == 0、ch == nil、反射判空全都不行——这些和关闭状态完全无关。唯一被语言规范保证的检测方式,是执行一次接收并检查第二个返回值:v, ok := 。关闭后,它立即返回零值且 <code>ok == false;未关闭时 ok == true。
- 有缓冲 channel:关闭后还能读出剩余缓存数据,
ok为true直到缓冲区清空 - 无缓冲 channel:关闭后下一次接收就直接
ok == false - 在
select中检测必须用case v, ok := ,不能只写 <code>case v := ,否则会静默拿到零值
为什么不能用 panic/recover 判断关闭状态
向已关闭 channel 发送会触发 panic: send on closed channel,但这个 panic 是故意不可 recover 的设计。它不是运行时异常,而是编译期/逻辑期错误信号——Go 要你提前规避,而不是事后兜底。
-
recover()成本高,比普通接收慢 1–2 个数量级 - 即使捕获成功,你也丢失了「谁关的、何时关的、还有没有 goroutine 在发」等关键上下文
- 真实场景中,worker goroutine 收到关闭信号应主动退出,不是靠发一次失败才醒悟
close() 是原子操作,但不解决竞态逻辑
close(ch) 本身是原子的:运行时加锁、置 closed=1、唤醒等待 goroutine。但它只保证「关」这一步不崩溃,不保证「该不该关」「有没有人还在发」。
- 多个生产者都调用
close(ch)→panic: close of closed channel - 一个生产者关完,另一个立刻执行
ch → <code>panic: send on closed channel -
sync.Once只防重复关,不防「关早了」:仍有 goroutine 持有 channel 引用并试图发送
真正容易被忽略的点:关闭 ≠ 立即停止通信
channel 关闭后,接收端仍可能读到合法数据(缓冲区残留),也可能读到零值(缓冲已空 + 关闭)。而这个零值和业务零值(如 0、"")无法区分——除非你每次都检查 ok。
-
for range ch会等缓冲耗尽才退出,不是 close() 后立刻停 - 想「一关就停」,得用
select+ok手动 break - 发送端 close() 后,必须确保所有发送 goroutine 已退出(靠
context+sync.WaitGroup),不能依赖 close() 自动同步











