go 的 pipeline 不是链式调用,square(gen(nums)) 会死锁——gen() 仅返回未被消费的 channel,无 goroutine 读取时,无缓冲 channel 立即阻塞,导致所有 goroutine 休眠而死锁。

Go 的 pipeline 不是链式函数调用,square(gen(nums)) 这种写法必然卡死——它根本没启动任何 goroutine,上游往 channel 写,下游还没开始读,无缓冲 channel 立刻阻塞,所有 goroutine 休眠,触发 fatal error: all goroutines are asleep - deadlock。
为什么 gen() 返回 channel 却不工作
因为 gen() 只是返回一个未被消费的 chan int,就像造好水管却不接水龙头。channel 本身不运行逻辑,真正干活的是它内部的 goroutine;如果没人用 go 启动消费者去 range 或 它,那它就是个“哑管道”。
-
gen()函数里必须用go func() { ... close(out) }()启动协程,否则数据永远发不出去 - 下游 stage(如
square())也必须自己启动 goroutine 处理输入,不能指望调用方同步执行 - 所有 stage 的输入 channel 都要由上游显式关闭,下游才能靠
for v := range in安全退出
每个 stage 必须显式启动 goroutine
常见错误是把 stage 当成普通函数调用:square(gen(nums)) 或 go square(gen(nums)) —— 前者完全同步,后者只启了一个 goroutine,但 square 内部仍是同步读写,依然会卡在 in 上。
- 正确做法:每个 stage 函数内部都封装
go func(),例如square()要自己启 goroutine 读in、算平方、写out - stage 函数签名应统一为
func(in ,返回新 channel,不复用输入 - 别在 stage 外层用
go stage(in, out)替代内部 goroutine —— 这会让 channel 生命周期失控,容易漏关或早关
关闭 channel 的时机和位置很关键
defer close(out) 放在 stage 函数体末尾是典型陷阱:函数可能提前 return(比如过滤阶段没数据可发),但 defer 仍会执行,导致下游 range 立即退出,而上游还在往已关闭的 out 发送 —— panic 就在这儿。
- 必须把
close(out)放在 goroutine 内部、所有发送路径结束后(例如for range in循环结束之后) - 如果 stage 有多个退出分支(如 error、ctx.Done()、空输入),每个分支都要确保
out被关闭,或统一用defer包裹在 goroutine 内 - 下游绝不能自己关输入 channel —— 输入由上游负责关闭,下游只读不关
缓冲区不是救命稻草,调试阶段建议禁用
加 make(chan int, 100) 能让程序“跑起来”,但只是掩盖问题:上游猛灌、下游慢吞,缓冲区越堆越大,最终 OOM;而且背压信号消失,你根本不知道哪一环已经拖后腿了。
- 开发和单元测试时一律用
make(chan int)(无缓冲),让阻塞立刻暴露数据流节奏问题 - 上线后加缓冲,仅限明确速率匹配且延迟敏感场景(如日志采样),大小按 P99 处理耗时 × 吞吐预估,别拍脑袋设 10000
- 缓冲 ≠ 背压:真正的背压靠下游消费速度反向约束上游,不是靠 buffer 吞掉压力
最易被忽略的其实是空输入和 panic 恢复后的状态一致性:第一个 stage 如果输入 slice 为空,必须主动 close(out),否则下游永远等;stage 内 recover 后若继续发数据,下游收到的可能是脏值或零值,必须严格判断是否还能写 out。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











