必须启动独立goroutine,否则for range会阻塞调用方导致pipeline串行化;正确做法是用go func(){...}()实现读、处理、写完全异步。

每个阶段必须启动独立 goroutine
直接在函数体里写 for range in 会阻塞调用方,导致整个 pipeline 串行化。上游还没发完数据,下游就卡死不动——这不是流式,是同步调用。
正确做法是显式用 go func() { ... }() 启动新协程,让读、处理、写完全异步。哪怕是最简单的平方函数,也得包一层 goroutine:
func Square(in
- 不加
go,调用Square(ch)就会立刻阻塞,直到ch关闭 -
defer close(out)必须在 goroutine 内部,否则函数返回时协程可能还没跑完 - 输入类型用
(只读),输出用chan(只写),避免误写
关闭输出 channel 的时机不能错
下游用 for x := range out 等不到 close(out) 就永远阻塞,这是 pipeline 卡死最常见原因。
关闭动作必须满足三个条件:在 goroutine 内、在所有数据发送完毕后、且不依赖上游是否关闭——因为上游可能提前退出(比如超时),但当前阶段仍需把已读到的数据发完再关。
- 错误写法:
defer close(out)放在函数开头 → 协程还没启动就关了 - 更错写法:在
for循环里每次发完一个值就close(out)→ panic: send on closed channel - 正确写法:把
defer close(out)放在 goroutine 匿名函数末尾,for结束后自然执行 - 如果阶段支持提前退出(如带
context.Context),要在select分支里确保close(out)仍被执行
用 select + default 防止向无人接收的 channel 发送
下游可能只取前 N 个结果就退出了,此时 out 虽未关闭,但已无 goroutine 接收。直接 out 会导致当前 stage 永久阻塞。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
非阻塞发送是刚需,尤其在过滤类阶段(大量数据被跳过):
for v := range in {
if f(v) {
select {
case out
-
default分支不是“丢数据”,而是让当前 stage 及时退出,靠defer close(out)通知下游流结束 - 别用
if len(out) 判断缓冲区——竞态且不可靠 - 若需保证不丢数据,应改用带 context 的版本,在
select中监听ctx.Done()
context.Context 必须参与每个阶段的阻塞点
没有 context.Context 的 pipeline 无法响应取消或超时。比如某个 stage 正在做 HTTP 请求,上游已超时,但它还在等响应,goroutine 就泄漏了。
关键不是“传进去”,而是在每个可能阻塞的地方用 select 监听 ctx.Done():
- 读输入:
case v, ok := 和case 必须同级 - 写输出:不能直接
out ,要用select { case out - 不要只在 goroutine 开头检查
ctx.Err()—— 万一卡在 channel 操作上,就收不到信号 - 所有 stage 的函数签名都该带
ctx context.Context参数,而不是靠全局变量或闭包捕获
真正难的不是写对一个 stage,而是所有 stage 对 ctx 的响应粒度一致:上游一取消,下游每个环节都要在毫秒级内释放资源、关闭自己的输出 channel,并退出 goroutine。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










