Go的pipeline不是链式调用,而是靠goroutine+channel+显式关闭构成并发数据流;sq(gen(nums))看似链式,实则仅是函数返回值嵌套,必须每个stage内部启动goroutine并正确关闭channel,否则必然死锁。

Go 的 pipeline 不是靠“链式调用”流转,而是靠 goroutine 启动 + channel 传递 + 显式关闭构成的数据流。写成 sq(gen(nums)) 看似链式,实则只是函数返回值嵌套,真正流转发生在每个 stage 内部的 goroutine 里——漏掉任何一个 goroutine 启动或 channel 关闭,整个流水线就卡死。
为什么 sq(gen(nums)) 能“看起来”链式,但实际不能靠它驱动流转
这种写法只是把前一阶段的输出 channel 直接传给下一阶段函数,语法上紧凑,但背后没有自动调度、没有生命周期管理、不带错误或取消语义。它能跑通的前提是:每个 stage 都自己 go func() { ... }() 启动 goroutine,并在处理完后 close(out)。
- 如果某个 stage 忘了
go,比如写成func square(in —— 这会阻塞在 <code>out ,因为没 goroutine 接收,直接 deadlock - 如果 stage 返回的是无缓冲 channel,而下游读得慢,上游就会在
out 处挂起,整条链停摆 -
sq(gen(nums))本身不启动任何 goroutine;它只是返回一个未被消费的 channel,必须有接收方(比如for range或)才能触发上游发送
数据真正开始流转的三个必要动作
pipeline 的“动起来”,依赖每个 stage 主动完成三件事:启动 goroutine、从输入 channel 持续读、往输出 channel 持续写并最终关闭。缺一不可。
- 输入 channel 必须用
for v := range in读取 —— 它会等上游close(in)后自动退出循环;用for { v, ok := 也行,但容易漏掉 <code>ok == false判断 - 输出 channel 必须由该 stage 的 goroutine 自己
close(out)—— 不能靠 GC,也不能由下游关;否则下游range out永远等不到 EOF - 每个 stage 必须显式启动 goroutine:
go func() { ... }()—— 否则逻辑会同步阻塞,无法形成并发流水线
中间 stage 不关 channel、不启 goroutine 的典型死锁场景
常见于想“优化”而删掉 goroutine,或误以为“channel 会自动转发”。例如:
立即学习“go语言免费学习笔记(深入)”;
func double(in <-chan int) <-chan int {
out := make(chan int)
// ❌ 错误:没 go,没 close,没读完 in
for n := range in {
out <- n * 2 // 卡在这儿:out 是无缓冲,没人读
}
return out
}此时调用 double(gen([]int{1,2})) 会立即 hang 在第一个 out 。正确写法必须包一层 goroutine 并 defer close:
func double(in <-chan int) <-chan int {
out := make(chan int)
go func() {
defer close(out) // ✅ 确保无论怎么退出,out 都会被关
for n := range in {
out <- n * 2
}
}()
return out
}什么时候该用缓冲 channel,而不是默认无缓冲
无缓冲 channel 要求发送和接收“同时就绪”,对时序极其敏感,适合 stage 处理极快、数据量小的调试场景。真实 pipeline 中,应主动设缓冲,否则一个 stage 暂停,整条链就断。
- 缓冲大小不是越大越好:
make(chan int, 1000)可能导致内存堆积,尤其当下游消费慢时;建议从16或64起步,压测后调整 - 所有 stage 的输入/输出 channel 都应设缓冲,不只是主干;fan-out 的 worker 输入 channel 同样需要缓冲,否则 worker 启动前数据就丢了
- 缓冲不能替代背压控制 —— 它只是缓解瞬时抖动;真正的背压靠下游消费速度反向约束上游生产,这需要 context 控制或限速逻辑配合
最容易被忽略的点是:pipeline 的“结束”不是由最后一个 range 决定的,而是由第一个 stage 是否发完 + 所有中间 stage 是否关掉自己的 out 共同决定的。少关一个 channel,下游就永远卡在 for range 里,goroutine 泄漏也就开始了。


















