向已关闭的 channel 发送数据会直接 panic,错误信息为“panic: send on closed channel”,这是 go 运行时强制终止 goroutine 的不可忽略错误;用 recover() 捕获属反模式,会导致逻辑错误隐蔽、性能开销大、破坏错误传播契约;安全写入应使用 select + nil channel 动态控制。

向已关闭的 channel 发送数据会直接 panic
Go 运行时强制禁止该行为,错误信息固定为 panic: send on closed channel。这不是可选警告,而是立即终止当前 goroutine 的运行时错误。它无法被忽略,除非用 recover() 捕获——但这么做属于反模式,不解决根本问题,只掩盖缺陷。
为什么不能靠 recover() 容错写入
用 recover() 捕获 send on closed channel 虽技术可行,但实际带来三重风险:
- 它把本应暴露的逻辑错误(如写入者未及时感知关闭信号)转为静默失败,调试成本陡增
-
recover()触发栈展开,性能开销比普通条件判断高 10 倍以上 - 破坏 Go 的错误传播契约:channel 关闭是明确的生命周期终点,不该用异常处理控制流
真正安全的写入控制方式:select + nil channel
当写入方需响应外部关闭信号(如超时、取消),正确做法是在 select 中将目标 channel 动态置为 nil:
func writer(done <p>关键点:<code>writeCh = ch</code> 放在 <code>default</code> 分支中,确保只要 <code>done</code> 就绪,<code>writeCh</code> 就保持 <code>nil</code>,导致第二个 <code>select</code> 的发送分支永远不可选,从而跳过写入。</p><h3>最容易被忽略的细节:close() 不等于“资源清理完成”</h3><p>关闭 channel 只通知“不再有新数据”,它不释放内存、不唤醒阻塞的发送者、也不终止 goroutine。若你依赖 <code>for range ch</code> 退出后就认为所有写入已结束,而实际还有 goroutine 正在执行 <code>ch (尤其无缓冲 channel),就会 panic。必须用 <code>sync.WaitGroup</code> 或 <code>context</code> 显式等待所有写入 goroutine 真正退出,再调用 <code>close()</code>。</code></p>











