Go pipeline必须用goroutine+channel手动构建并发数据流,直接链式调用如sq(gen(2,3))因缺少goroutine导致死锁;gen返回的是

Go 里 pipeline 不是语法糖,也不是库函数,而是靠 goroutine + chan 手动拼出来的数据流;写错最直接的表现就是 fatal error: all goroutines are asleep - deadlock,或者某阶段永远卡在 range in 里不退出。
为什么 stage 函数必须启动 goroutine 而不能直接调用
常见错误是把 sq(gen(2, 3)) 当作 pipeline 启动,但这是同步调用:上游 gen 往 out 写,下游 sq 还没启动 goroutine 去读,out 阻塞,整个流程挂死。
-
gen返回的是,但它本身不消费任何 channel —— 它只发,不收 - 每个 stage 必须自己
go func() { ... }(),否则没有并发,也没有数据流动节奏 - 无缓冲 channel 要求“发送和接收同时就绪”,缺一个就卡住;加缓冲只是掩盖问题,不是解决阻塞
channel 关闭时机不对会导致 panic 或漏数据
关闭 out 的位置错了,轻则下游提前退出、丢数据,重则触发 panic: send on closed channel。
- 只由发送方关闭自己的
out,且仅一次 —— 放在 goroutine 内部、所有out 语句之后 - 别用
defer close(out):如果上游提前关闭in,stage 提前 return,defer仍会执行,导致下游range立刻退出,而上游可能还在往已关的out写 - 中间 stage(如过滤)即使跳过部分输入,也必须确保
out被关闭;否则下游for v := range in永远等不到 EOF - 别关
in—— 输入 channel 应由上游关,stage 只负责读完后关自己的out
如何让 pipeline 支持 cancel 和 error 传播
原生 chan 没有错误语义,close() 只表示“数据结束”,不区分成功或失败;不加控制的 pipeline 一旦出错就会卡死或静默丢弃。
立即学习“go语言免费学习笔记(深入)”;
- 每个 stage 函数签名必须带
ctx context.Context,并在select中监听ctx.Done(),不能只依赖range in - 错误走单独的
errCh chan error,不要和数据混在一个 channel 里;任意 stage 发现错误,应立即close(out)并发 error - 主调方用
select同时监听out和errCh,收到 error 后调用cancel()终止整个链 - 别在 stage 内部
recover()吞 panic —— 它跨不了 goroutine,且会让错误不可见
缓冲 channel 在 pipeline 中的实际影响
make(chan int, 100) 看似能缓解短时抖动,但会掩盖背压缺失,上线后容易内存暴涨甚至 OOM。
- 开发调试阶段一律用无缓冲 channel:
make(chan int)—— 让阻塞立刻暴露,倒逼你理清数据流节奏 - 上线后加缓冲,只在明确知道上下游速率匹配、且延迟敏感(如实时日志采样)时才考虑
- 缓冲大小不是越大越好:
len(ch)不反映写入状态,设成 10000 只会让 OOM 提前,而不是逻辑更健壮 - 有界缓冲 ≠ 背压控制:真正的背压靠下游消费速度反向约束上游,不是靠 buffer 吞掉压力
真正难的不是写几个 go func(),而是每个 stage 的生命周期是否与 channel 关闭严格对齐;漏关一个 out,整条链就卡在某个 goroutine 里不动,pprof 一看全是 chan receive —— 这种泄漏不会报错,只会悄悄吃光内存。


















