根本原因是未妥善处理 channel 的关闭时机和 goroutine 泄漏。常见表现为程序卡死、pprof 显示大量 goroutine 堵塞在 channel 操作上。

为什么用 chan 做 pipeline 容易卡死
根本原因不是 channel 本身,而是没处理好“关闭时机”和“goroutine 泄漏”。常见现象是程序跑着跑着不动了,pprof 显示一堆 goroutine 堵在 上。这通常发生在某一级 stage 没收到关闭信号,却还在试图从上游读;或者上游关了 channel,下游没检查 <code>ok 就继续读。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 每个 stage 都要按「接收 → 处理 → 发送」流程写,并在循环里用
for v, ok := 判断上游是否关闭 - 不要在 pipeline 中间层主动
close(out)—— 只有最末级或明确负责收尾的 stage 才该关输出 channel - 如果某 stage 可能提前退出(比如过滤掉所有数据),要用
defer close(out),否则下游永远等不到 EOF
pipeline 中怎么传错误而不是 panic
channel 本身不带错误语义,硬塞 error 进数据 channel 会污染类型、增加判断负担。正确做法是把错误流单独拆出来,或者用结构体封装结果。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 避免用
chan interface{}或chan *MyResult混装成功/失败 —— 类型模糊,调用方必须做断言或空值检查 - 推荐组合:一个数据 channel + 一个
chan error,由主控 goroutine 统一收集(注意别漏读,否则可能阻塞) - 更稳妥的是定义
type Result struct { Data T; Err error },让每个 stage 输出chan Result,调用方用if r.Err != nil分支处理
多级 goroutine 套娃导致内存暴涨怎么办
每加一级 pipeline,就多一层 goroutine + channel 缓冲,尤其当输入量大、中间 stage 处理慢时,未消费的数据全堆在 channel 缓冲区里。现象是 RSS 持续上涨,runtime.ReadMemStats 显示 Mallocs 累积很快。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 所有 channel 创建时显式指定缓冲区大小:
make(chan int, 64),别依赖 0 缓冲(同步 channel)—— 它会让上下游强耦合,一卡全卡 - 对计算密集型 stage(比如 JSON 解析、正则匹配),加
runtime.Gosched()防止单个 goroutine 占满 P,影响调度 - 用
context.Context控制整条 pipeline 生命周期,超时或取消时,所有 stage 要能响应ctx.Done()并快速退出
Go 1.22+ 的 iter.Seq 能替代 pipeline 吗
不能直接替代。 iter.Seq 是为「顺序迭代」设计的,本质是函数式接口(func(yield func(T) bool)),它不启动 goroutine,也不管理并发,更不提供 stage 间解耦能力。你拿它串几个 Map、Filter,还是单线程执行。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 如果场景是纯 CPU-bound、无 I/O、数据量小,用
iter.Seq更轻量、GC 压力更低 - 但只要涉及 HTTP 请求、DB 查询、文件读写,或者需要控制并发数(比如最多 5 个 worker 同时处理),就必须回到
chan+goroutine的 pipeline 模式 - 混用可以:用
iter.Seq做初始数据生成,再喂给 channel pipeline 做并发处理
真正难的从来不是写通一条 pipeline,而是当某一级突然变慢、某次请求返回异常、某个 channel 缓冲区被撑爆时,你能一眼看出堵在哪、谁没关 channel、错误从哪漏出去的。这些地方没日志、没监控、没 context 超时,光靠 go tool trace 也很难定位。










