扇入扇出是go原生goroutine与channel组合的并发模式;fanin死锁或丢数据主因是select轮询未关闭channel或闭包捕获循环变量;正确做法是为每个输入channel启独立goroutine,用for range自动退出,并以sync.waitgroup同步关闭输出channel。

扇入扇出不是框架提供的功能,而是 Go 原生 goroutine 与 channel 组合出的并发模式。你不需要引入任何框架,直接用标准库就能安全、高效地实现——但必须避开几个高频踩坑点。
fanIn 函数为什么总死锁或丢数据?
典型错误是用 select 轮询多个未关闭的 chan int,或者在闭包里直接捕获循环变量 ch。结果要么 fatal error: all goroutines are asleep - deadlock,要么只从最后一个 channel 读数据。
- 每个输入 channel 必须起独立
goroutine,内部用for v := range c—— 它会在 source channel 关闭时自动退出 go func(c 必须显式传参,不能写成 <code>go func(){ ... ch ... }(),否则所有 goroutine 共享同一个ch变量- 输出 channel
out不能由任意一个转发 goroutine 关闭;要用sync.WaitGroup等待全部完成,再由额外 goroutine 关闭 - 如果输入 channel 可能永远不关闭(比如日志流),需配合
context.Context控制生命周期
fanOut 怎么避免 goroutine 泄漏和竞争?
扇出本质是让多个 worker 从同一个 chan 拉任务,不是“复制 channel”。常见误操作是为每个 worker 新建一个 channel 并手动分发,这反而增加同步复杂度。
- worker 启动逻辑要统一:用
for i := 0; i ,所有 goroutine 共享读 <code>in -
in通道建议带缓冲(如make(chan Task, 100)),防止生产者因无人消费而阻塞 - worker 内部不要用
select非阻塞读取,除非你明确需要跳过空闲轮次;普通场景直接for task := range in更简洁可靠 - 关闭
in前,确保所有 worker 已退出;否则残留 goroutine 会卡在range上,导致WaitGroup永远等不到
扇入扇出组合使用时,result channel 的类型怎么设计?
别只传原始值。当多个 worker 并行处理时,你一定需要知道「谁返回了什么」,否则无法关联任务与结果,也难以处理错误。
- 定义结构体:
type Result struct { ID int Data interface{} Error error } - worker 发送前填充
ID(比如 worker 编号或任务唯一标识),接收方靠它做排序或重试 - 不要用
chan error单独传递错误 —— 它和结果不同步,容易错位;Error字段进Result才能保证原子性 - 如果结果量极大,考虑加缓冲:
make(chan Result, 1000),避免 worker 因结果 channel 满而阻塞
最易被忽略的是关闭时机链:任务 channel 关闭 → worker 退出 → WaitGroup 计数归零 → 结果 channel 关闭。任何一个环节提前或遗漏,都会引发死锁、panic 或 goroutine 泄漏。别依赖“看起来跑通了”,用 pprof 查 goroutine 数量,才是真实压测下的第一道验证。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











