fanIn 函数需手动关闭输出 channel,否则主 goroutine 的 for range 会永久阻塞;正确做法是用 sync.WaitGroup 等待所有输入 channel 消费完毕后再 close(out),动态 channel 需避免提前关闭,固定少量 channel 推荐 select + nil 检测。

fanIn 函数必须手动 close 输出 channel
不 close out,主 goroutine 用 for range out 会永远阻塞。常见错误是只启动读取 goroutine,却忘了等所有输入 channel 结束后触发 close(out)。正确做法是用 sync.WaitGroup 跟踪每个输入 channel 的消费完成状态,wg.Wait() 后再 close(out)。如果输入 channel 是动态生成的(比如来自配置),别在循环外提前 close,否则部分数据收不到。
多个固定 channel 用 select + nil 检测更轻量
当输入 channel 数量少且固定(比如 2~4 个),直接写 select 比反射或 WaitGroup 更快、无额外内存分配。关键点是每次收到数据后检查 ok,关闭的 channel 要设为 nil,这样下次 select 就自动跳过它:case v, ok := 。注意:<code>nil channel 在 select 中永远不可读,所以能自然退出;但所有输入都为 nil 后必须主动 break,否则死循环。
动态数量 channel 必须用 reflect.Select
select 语句不支持运行时决定 case 数量,硬编码 case 数上限会导致扩展性差。此时要用 reflect.Select:先构造 []reflect.SelectCase,每个元素对应一个输入 channel 的 recv 操作;循环调用 reflect.Select(cases),返回索引 i 和接收到的值;若 ok == false(通道已关且空),就从 cases 切片中删掉该 case。容易漏的是:删 case 后要重新 slice,不能只置空字段;且 reflect.Select 本身有小开销,QPS 超 10k 时建议预分配 cases 切片并复用。
缓冲区大小和关闭时机决定是否丢数据
输出 channel 的缓冲容量必须 ≥ 最大可能并发写入数,否则写入会阻塞甚至死锁。例如 8 个 goroutine 同时往 out := make(chan int, 4) 写,必然卡住。更隐蔽的问题是关闭太早:某个输入 channel 还没读完,wg.Wait() 就返回了(因为 goroutine 启动后立刻 wg.Done(),但还没开始读),导致 close(out) 提前触发,后续数据被丢弃。解决方法是把 wg.Done() 放在 for range ch 循环结束后,而不是 goroutine 启动后。
立即学习“go语言免费学习笔记(深入)”;
实际聚合逻辑里最常被忽略的,是输入 channel 关闭与输出 channel 关闭之间的“时间窗”——这个窗口哪怕只有几毫秒,也足以让一部分数据落空。务必确保所有消费者 goroutine 真正退出后再关输出 channel,而不是仅仅启动完就认为“任务结束了”。


















