因为close(out)放goroutine外会导致上游仍在写入时channel已关闭,下游range立即退出而上游继续发送引发panic;正确做法是将close(out)放在goroutine内、所有out<-操作完成后统一执行。

为什么 pipeline 里 close(out) 放在 goroutine 外会 panic
常见错误是把 close(out) 写在 stage 函数体末尾,比如:
func sq(in <-chan int) <-chan int {
out := make(chan int)
go func() {
for n := range in {
out <- n * n
}
}()
close(out) // ❌ 错!goroutine 还没发完,这里就关了
return out
}这会导致下游 for v := range out 立刻退出,而上游 goroutine 还在往已关闭的 out 写——触发 panic: send on closed channel。根本原因是:关闭动作必须发生在所有发送操作完成之后,且只能在 goroutine 内部做。
- 所有发送路径(包括
return前的提前退出)之后,才调用close(out) - 不能依赖函数返回顺序,goroutine 启动后立即返回,函数体后续代码和它并发执行
- 若 stage 有错误分支(如解析失败),也要确保每个出口都覆盖
close(out)
中间 stage 的输入 channel 谁来关、什么时候关
中间 stage 的 in 是只读通道(),它由上一阶段的生产者负责关闭。你作为中间 stage 的实现者,只读不关;关早了,<code>for n := range in 会提前退出,漏掉后续数据;关晚了,上游已经结束却还在等你读,造成死锁。
- 上游 stage 必须在自己所有发送完成后调用
close(in)(且仅一次) - 中间 stage 用
for n := range in安全读取,无需判断ok,也不应自己关in - 如果上游因错误提前关闭(比如网络断开),你的 stage 应尽快清理并关掉自己的
out,但不能反向关in
多个 worker 并行消费同一输入时,怎么安全关输出 channel
当一个 stage 启动多个 goroutine 从同一个 in 读取(扇入),它们共同往同一个 out 写,这时谁关 out、何时关,就容易出错。典型症状是下游 range out 永不退出,或部分 worker 还在写时就被关了。
立即学习“go语言免费学习笔记(深入)”;
- 不能让任意一个 worker 自作主张
close(out),否则其他 worker 写入即 panic - 要用
sync.WaitGroup或errgroup.Group等待所有 worker 退出后再统一关out - worker 内部需监听
in关闭,并在退出前完成自己那部分发送,再通知 WaitGroup - 如果某个 worker 遇到不可恢复错误,应通过额外的
done通道广播终止信号,避免个别卡死拖垮整个 pipeline
缓冲 channel 关闭后 len(ch) 还是原来值,但这不表示还能读
关闭带缓冲的 channel 后,len(ch) 返回剩余未读数据个数,cap(ch) 不变,但这些数字只是快照——它们不反映下游是否已消费完毕,也不代表你能继续安全写入。很多同学误以为“还有数据可读,说明 channel 没真关”,于是绕过 for range 自己手写 v, ok := 循环,结果漏掉最后一次 <code>ok == false 判断,导致 goroutine 卡住。
-
for v := range ch是唯一能自动感知关闭并退出的读法,无论有无缓冲 -
len(ch)只适合调试观察,不能用作逻辑判断依据 - 即使
len(ch) > 0,只要close()已被调用,后续接收最终都会返回零值 +false
真正难的不是写 close() 这一行,而是确认“所有该发的数据都发完了,且所有接收方都确认收到了”。这个确认过程必须靠显式同步(WaitGroup、context、done channel),而不是靠 channel 状态本身。


















