根本原因是channel生命周期管理不当:未关闭、关早或未等消费者读完。go中无缓冲channel发送时会阻塞直至有接收方,若stage未关闭out而下游用range等待,则上游永久卡在out。

流水线卡住、数据不往下走,八成是 channel 生命周期没管好——不是没关,就是关早了,或者根本没等消费者读完。
为什么数据在中间 stage 就停住了?
Go 的 chan 默认是同步的:往里 send 时,会一直阻塞,直到有 goroutine 在另一端 recv。如果某个 stage 没关闭自己的输出 out,而下游又用 for x := range in 等着它关,那上游就永远卡在 out 这一行。
- 常见错误现象:
generator发完所有数据后程序退出,但square或printresults完全没执行 - 根本原因:主 goroutine 没等 pipeline 走完就结束了;或某个 stage 启动了 goroutine 却没用
defer close(out),导致下游range永远不退出 - 正确做法:每个 stage 函数内部必须启动 goroutine,并在其结束前
close(out);主函数要用sync.WaitGroup或done chan struct{}等待完成
如何保证多个 stage 严格按顺序消费?
别指望靠“谁先启动谁先写”来保序——goroutine 调度不可控。真要顺序,就得放弃让多个 stage 并发写同一个 channel,改用「多通道 + 主协程顺序读」。
- 适用场景:比如解析文件头/体/尾,结果必须按 Header → Body → Footer 输出
- 错误思路:让
parseHeader、parseBody全往一个out chan []byte写 —— 顺序无法保证 - 正确做法:为每个阶段分配独立 channel(
headCh,bodyCh,footCh),主 goroutine 按需依次、<code>、<code> - 优势:零锁、零竞争、逻辑清晰;劣势:不能并行消费结果(但本来就要顺序)
怎么让 pipeline 支持提前终止和错误传递?
原生 chan 没有错误语义,close() 只代表“数据结束”,不区分成功还是失败。靠 panic 传错误既不可控也不跨 goroutine。
- 简单方案:用两个 channel ——
out chan T和errCh chan error,下游用select同时监听 - 推荐方案:统一用
chan Result,其中type Result struct { Value T; Err error } - 关键约束:任意 stage 遇错,应立即
close(out)并发 error 到errCh;上游收到 error 后,必须停止向后续 stage 发数据(通常靠ctx.Done()控制) - 切忌:在 stage 内部用
recover()拦截 panic 来模拟错误流 —— 它不会传播到其他 goroutine,且掩盖真实崩溃点
context.Context 该传给谁、怎么传?
Context 不是装饰品,传错位置等于没传:只在最外层创建、内部硬编码,会导致 cancel 失效;在 stage 里用 context.WithTimeout 覆盖父 ctx,会让外部超时失效。
- 必须作为参数传给每个 stage 函数,例如
func filter(ctx context.Context, in - 每个 stage 内部必须用
select主动响应ctx.Done(),不能只靠读 input channel 自然退出 - 若 stage 内调用了 HTTP 请求,必须用
http.NewRequestWithContext(ctx, ...),否则网络调用不响应 cancel - 如需子超时(比如某 stage 最多处理 5 秒),用
context.WithDeadline(parent, deadline),而不是WithTimeout覆盖
最容易被忽略的一点:缓冲 channel 的容量不是越大越好。设成 make(chan T, 1000) 看似防阻塞,实则可能掩盖背压问题,让上游狂塞、下游处理不过来、内存悄悄涨上去。合理值应基于吞吐预期与单条数据内存开销权衡——宁可小一点,靠 ctx 或重试机制兜底,也别盲目堆 buffer。











