无缓冲channel在pipeline中容易卡死,因其要求发送方和接收方必须同时就绪;而各阶段处理耗时不均(如stage1读文件快、stage2解析json慢),导致stage1在向out写入时因stage2未就绪而永久阻塞。

必须用有界缓冲 channel,且每个阶段独立 goroutine + 显式 close 输入 channel,否则大概率死锁。
为什么无缓冲 channel 在 pipeline 里容易卡死
无缓冲 chan int 要求发送方和接收方“同时就绪”,而 pipeline 各阶段处理耗时不均——比如 stage2 解析 JSON 比 stage1 读文件慢得多,stage1 就会一直阻塞在 out ,等 stage2 来收;但 stage2 还没启动或正在忙,整个流水线就挂住。
常见错误现象:fatal error: all goroutines are asleep - deadlock,尤其出现在 stage 数 ≥3 或某阶段含 I/O、计算密集操作时。
- 无缓冲只适合极简 demo(如
gen → sq → main三步纯内存运算) - 真实场景一律用有界缓冲,例如
make(chan int, 100) - 缓冲容量不是越大越好:设成
10000等于放弃背压,OOM 风险陡增;设成1则退化为半同步,抖动容忍度低
每个 stage 必须是独立函数 + goroutine + 显式 close
正确写法是每个阶段封装为函数,接收输入 channel,返回输出 channel,内部用 go func() { ... }() 启动,并在循环结束后 close(out)。
错误写法包括:在同一个 goroutine 里串行调多个 stage、漏掉 close(out)、或在 stage 内部对同一 channel 既读又写。
- 输入 channel 由前一阶段或源头关闭,当前 stage 只需
for v := range in - 输出 channel 必须由当前 stage 的 goroutine 关闭,不能由调用方关
- 如果 stage 可能提前退出(如解析失败),也要确保
close(out)执行,否则下游range永远等不到 EOF
如何安全传递错误并支持提前终止
原生 channel 不带错误语义,chan int 无法表达 “这里出错了,后面别处理了”。硬塞零值或特殊标记(如 -1)极易和业务数据混淆。
推荐两种方式,选其一即可:
- 加一个
chan error:只允许第一个出错 stage 发送一次,其他 stage 在select中监听它,收到即return,不再往下游发数据 - 用
context.Context:每个 stage 接收ctx context.Context,在for循环里select检查ctx.Done(),出错时调cancel()
注意:不要在 stage 内部 recover panic 后继续往 out 发数据——下游拿到的是脏值,不是错误信号。
fan-out / fan-in 怎么写才不泄漏 goroutine
fan-out 是把一个 input channel 拆给 N 个并行 worker;fan-in 是把 N 个 output channel 合成一个。Go 标准库没提供现成函数,手写时最易漏掉 channel 关闭逻辑。
- fan-out:用
for i := 0; i 启 N 个 goroutine,每个都 <code>for v := range in—— 注意这里in是同一个 channel,多个 goroutine 并发读是安全的 - fan-in:合并函数接收
[]chan int,为每个c启一个 goroutine 去for v := range c并转发到统一out;关键点是:所有输入c关闭后,才close(out),不能靠计数器判断 - 漏关
out会导致下游永远阻塞;过早关则丢数据
真正难的不是写法,而是确定谁负责关闭、何时关闭、以及如何让所有路径(正常结束/出错/取消)都走到关闭逻辑——这需要每个 stage 对自己的输出 channel 有完全控制权。











