fanout 函数中不能用 range in 遍历未缓冲 channel,因为多个 worker 并发 range 时调度不公,可能导致部分 goroutine 永久阻塞在接收端,且无法感知 close 信号,引发死锁。

Go 里的 fanOut 不是标准库函数,也没有“自动扩散”能力——它必须手动实现,且核心难点不在“怎么发”,而在“谁关 channel、何时关、怎么避免死锁”。直接用 range inCh 启多个 worker 会卡死,因为未缓冲 channel 的读取不可预测,部分 goroutine 永远等不到数据也等不到关闭信号。
为什么 fanOut 函数里不能用 range in 直接遍历输入 channel
未缓冲的 in channel 上,range 是阻塞式接收;多个 worker 并发 range in 时,Go 运行时无法保证公平调度——可能某个 worker 把所有数据读完,其余全部卡在 range 开头,永远收不到 close(in) 信号。更危险的是:如果上游忘了 close(in),所有 worker 都永久阻塞,触发 fatal error: all goroutines are asleep - deadlock。
- 必须改用
for { select { case v, ok := 结构,显式检查 <code>ok -
in的关闭责任必须由**上游生产者**承担,worker 只负责消费和退出 - 若需广播(每个 worker 都要收到全部数据),就不能靠共享一个
inchannel,得用复制逻辑(如循环写入多个输出 channel)
fanOut 返回多个 channel 时,如何安全关闭它们
典型场景是把一份输入数据分发给 N 个独立 consumer,每个 consumer 对应一个输出 channel。这时不能在 fanOut 函数末尾直接 close() 所有输出 channel——因为发送 goroutine 可能还没发完,就提前关了,导致后续 c panic:<code>send on closed channel。
- 关闭动作必须放在发送 goroutine 内部,在读完
in且所有v都成功写入各输出 channel 后再执行 - 每个输出 channel 建议设缓冲(如
make(chan int, lag)),避免 sender 因某个 consumer 处理慢而被卡住 - 若 consumer 可能提前退出(比如超时),要考虑是否加
select { case c 避免阻塞
用 sync.WaitGroup 等待所有 worker 完成再关 out channel
Fan-in 聚合阶段常出错:主 goroutine 在启动 worker 后立刻 close(out),但此时部分 worker 还没写完,导致漏数据或 panic。正确做法是让每个 worker 自己通知完成,主 goroutine 等齐后再关。
- 每个 worker 启动前调用
wg.Add(1),结尾defer wg.Done() - 聚合 goroutine 用
go func() { wg.Wait(); close(out) }(),不能同步调用wg.Wait()(会阻塞主流程) -
outchannel 必须带缓冲(如make(chan int, numJobs)),否则第一个 worker 写入就可能阻塞,导致整个 fan-out 卡住
真正容易被忽略的点是:fan-out 的“扩散”不是语义上的复制,而是行为上的协调——你得决定是轮询分发、广播分发,还是按 key 分片;每种选择对应不同的 channel 关闭时机、缓冲策略和错误传播方式。没想清楚这点,代码跑起来要么漏数据,要么死锁,要么内存暴涨。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











