直接用匿名函数启动 goroutine 会卡住 pipeline,因其在无缓冲 channel 上阻塞写入,I/O 慢导致缓冲满、上游挂起、CPU 下降、延迟飙升,并引发 deadlock 或 goroutine 僵死;须配带缓冲 channel、context.Context、select 超时控制、defer close 和 panic recover。
直接在 processData 里写 go func() { ... }() 启动下游处理,看似并发了,实际极易导致整条 pipeline 停摆。根本原因是:无缓冲 channel 写入时会阻塞 sender,而匿名函数若含 I/O(比如 http.Get)或解析逻辑,响应慢 → 缓冲区迅速填满 → 上游 goroutine 被挂起 → CPU 利用率掉、延迟飙升。
fatal error: all goroutines are asleep - deadlock 或大量 goroutine 处于 chan send 状态close(out),消费者永远等不到 EOF,for range ch 不退出匿名函数不是不能用,而是必须包裹在可控的执行环境里。每个 stage 的 goroutine 都应接收 ctx context.Context,并用 select 替代直写 channel。
out := make(chan int, 10) —— 太小(如 1)≈ 无缓冲;太大(如 10000)掩盖背压,且浪费内存ctx.Done():select { case out
close(out),且只 close 一次;关闭前确保所有发送完成,否则 panic不要为每条数据启一个匿名函数。高频调用下,几万条数据 = 几万个 goroutine,调度开销反超收益。应该复用固定数量的 worker,从带缓冲的任务 channel 消费。
jobs := make(chan Task, 1000),否则生产者一快就堵死runtime.GOMAXPROCS(0) * 2 ~ *4;若含 HTTP 调用,可略放大,但需同步调优 http.Transport.MaxIdleConns
select + ctx.Done(),panic 必须 recover,避免单点失败扩散errgroup.Group 启动所有 stage,统一 cancel 和错误传播,不用手搓 done channel很多人在 pipeline 最外层套一层 bufio.NewReader 就以为优化完成了,其实 buffering 的收益取决于数据粒度和协议特征。
立即学习“go语言免费学习笔记(深入)”;
net.Conn 接收侧,用 bufio.NewReaderSize(conn, 8192)
bufio —— 数据已解包,再 buffer 只是徒增内存拷贝os.OpenFile + bufio.Scanner 分块读,而非一次性读全再塞 channel缓冲区容量、context 取消、worker 复用这三点,漏掉任何一个,匿名函数就从“提速工具”变成“吞吐量杀手”。实际调试时,pprof 查 goroutine stack 和 channel blocking 点,比猜更快。