go 的 pipeline 必须用 goroutine + channel 实现并发数据流;直接链式调用因无 goroutine 启动下游消费者,导致上游写入 channel 时阻塞,引发死锁。

Go 的 pipeline 不是函数链式调用,而是靠 goroutine + channel 构建的并发数据流;写错最常卡死或漏数据,核心问题在 channel 关闭时机、缓冲策略和错误传播三处。
为什么直接 chain 调用会 fatal error: all goroutines are asleep - deadlock
新手常把 square(gen(2,3)) 当作 pipeline,但这是同步调用:上游 gen 往 channel 写,下游 square 还没启动 goroutine 去读,channel 阻塞,整个流程挂住。
-
gen返回的是,但若下游没用 <code>go启动消费者,它就只是个“没人监听的发射器” - 无缓冲
channel要求“发送方和接收方同时就绪”,缺一不可 - 每个 stage 必须显式启动
goroutine,不能依赖调用方顺序执行
如何正确关闭 channel 才不 panic 或漏数据
defer close(out) 放在 stage 函数体里看似合理,但极易出错:如果上游提前关闭输入,stage 提前 return,defer 还是会执行,导致下游 range 立刻退出,而上游可能还在往已关闭的 out 写——panic: send on closed channel 就在这儿。
- 不要在 stage 函数体里
close(out),而要在goroutine内部、所有out发送路径之后统一关 - 下游必须用
for v := range in安全读取,且前提是上游真关了;别自己关输入channel,否则 panic - 如果某个 stage 提前
panic或return(比如过滤掉全部数据),仍要负责close(out),否则下游永远等不到 EOF
要不要加缓冲 channel?加多大才安全
加 make(chan int, 100) 能缓解短时抖动,但会掩盖背压缺失——上游猛灌、下游慢吞,内存悄悄涨到 OOM 才暴露问题。
- 开发调试阶段一律用无缓冲
channel(make(chan int)),让阻塞立刻显现,倒逼你理清数据流节奏 - 上线后加缓冲,只在明确知道上下游速率匹配、且延迟敏感(如实时日志采样)时才考虑
- 缓冲大小不是越大越好:
len(ch)不反映写入状态,设成 10000 只会让 OOM 提前,而不是逻辑更健壮 - 有界缓冲 ≠ 背压控制:真正的背压靠下游消费速度反向约束上游,不是靠
buffer吞掉压力
如何让 pipeline 支持中断和错误传递
原生 channel 没错误语义,context.Context 也不能只包一层就完事。常见错误是 select 里只检查 ctx.Done() 开头,但 out 仍可能永久阻塞。
- 所有
channel操作必须套select,包含ctx.Done()和in/out分支 - 错误不能只
panic或log,必须通过额外的errCh chan error向上传播(注意:不要用同一个channel传数据和错误) - 每个 stage 的函数签名必须带
ctx context.Context参数,并在关键阻塞点检查ctx.Done() - 向输出
channel发送前也应select { case out
真正难的不是写出能跑通的 pipeline,而是处理空输入、提前 panic 恢复后是否还能发数据、多个 worker 共享输入时谁来关输出、以及 close 后有没有 goroutine 还在试图写——这些边界情况不显式覆盖,上线后只会以 goroutine 泄漏、内存暴涨或静默丢数据的方式爆发。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











