
本文讲解如何使用 sync.waitgroup 在 fan-in 模式下协调多个 goroutine 向同一输出通道写入数据,并在所有源通道读取完毕后优雅关闭输出通道,避免死锁。
本文讲解如何使用 sync.waitgroup 在 fan-in 模式下协调多个 goroutine 向同一输出通道写入数据,并在所有源通道读取完毕后优雅关闭输出通道,避免死锁。
Go 中的 fanIn 模式常用于将多个输入通道(如来自不同 goroutine 的数据流)合并为单一输出通道。但若未正确关闭输出通道,for range 会永远阻塞——这正是原代码发生死锁的根本原因:fanIn 返回的
问题核心不在于“是否 defer close”,而在于何时 close:必须确保所有上游 goroutine 完成读取并退出后,才关闭 out 通道。defer 仅适用于单 goroutine 生命周期内的清理,而 fanIn 涉及多个并发 goroutine,需跨 goroutine 协调完成信号。
✅ 正确解法是引入 sync.WaitGroup 进行同步:
import "sync" func fanIn(in ...<p>⚠️ 关键改进说明:</p>
- 移除嵌套 goroutine:原代码中 go func(c int) { out
- WaitGroup 精准计数:wg.Add(1) 在循环内调用,wg.Done() 在每个 worker goroutine 结束时触发,确保计数与实际 goroutine 数量严格一致;
- 关闭时机可控:wg.Wait() 阻塞直到所有 generator 数据读完并退出,此时再 close(out),保证 range 可正常终止;
- 关闭操作必须在 goroutine 中执行:因 wg.Wait() 是阻塞调用,若放在主线程将阻塞 fanIn 返回,故需另起 goroutine 执行。
? 补充建议:
- 若需更高级控制(如超时、取消),可结合 context.Context;
- 对于大量通道合并场景,考虑使用 errgroup.Group(支持错误传播和上下文取消);
- 始终对输出通道设置合理缓冲(如本例 make(chan int, 10)),缓解生产者/消费者速度差异带来的阻塞风险。
最终,该实现符合 Go 的并发哲学:通过通信共享内存,用同步原语(WaitGroup)明确表达协作逻辑,而非依赖隐式状态或竞态敏感的 defer 时机。











