Fan-out/Fan-in 的核心是安全关闭 channel 以避免 goroutine 泄漏;输入 channel 由发送方显式 close,worker 用 for-select 检测 ok 退出,输出 channel 在所有 worker 完成后关闭。

用 select + chan 实现基础 Fan-out/Fan-in
Go 里最直接的 Fan-out/Fan-in 就是启动多个 goroutine 消费同一个输入 channel(Fan-out),再把结果写进同一个输出 channel(Fan-in)。关键不是“怎么起 goroutine”,而是**怎么安全关闭 channel、避免 goroutine 泄漏**。
常见错误现象:fatal error: all goroutines are asleep - deadlock,或部分 worker 没收到关闭信号卡死。
- 输入 channel 必须由发送方显式
close(),worker 不能靠range自动退出——因为 Fan-in 需要所有 worker 完成后才关输出 channel - 每个 worker 应该用
for { select { case v, ok := 结构,而不是 <code>range in - Fan-in 的聚合 goroutine 要等所有 worker 退出后再
close(out),推荐用sync.WaitGroup计数
in := make(chan int, 10)
out := make(chan int, 10)
var wg sync.WaitGroup
<p>// Fan-out:启动 3 个 worker
for i := 0; i < 3; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for v := range in {
out <- v * v // 简单处理
}
}()
}</p><p>// Fan-in:收集结果并关闭
go func() {
wg.Wait()
close(out)
}()用 context.Context 控制 Fan-out 生命周期
真实场景中,worker 可能阻塞在 IO 或重试逻辑里,仅靠 channel 关闭不够。必须用 context.Context 主动中断。
使用场景:HTTP 请求分发、数据库批量查询、带超时/取消的任务编排。
立即学习“go语言免费学习笔记(深入)”;
-
context.WithTimeout或context.WithCancel创建子 context,传给每个 worker - worker 内部所有阻塞操作(如
http.Client.Do、time.Sleep)都要配合ctx.Done()检查 - 不要在 worker 里直接
close(in)或close(out)—— 这会破坏 Fan-in 的聚合逻辑 - 父 goroutine 在 cancel 后仍需等待
WaitGroup,否则可能漏掉已开始但未完成的 worker 输出
sync.Pool 和缓冲 channel 的取舍影响吞吐量
Fan-out/Fan-in 高频场景下,channel 缓冲大小和对象复用直接影响内存分配和 GC 压力。
性能影响:
- 无缓冲 channel(
make(chan T))会让 sender 和 receiver 强同步,降低并发度;但内存零额外开销 - 过大的缓冲(如
make(chan T, 10000))可能掩盖背压问题,导致 OOM;建议按平均 batch 大小 × worker 数预估 - 如果 worker 处理的是临时结构体(如
type Result struct{ ID int; Data []byte }),用sync.Pool复用能减少 20%+ GC 次数 - 注意
sync.Pool不保证对象一定被复用,且不能存含 finalizer 的对象
错误处理与结果聚合的常见陷阱
Fan-out 后,某个 worker panic 或返回错误,很容易让整个 Fan-in 流程失效或丢失上下文。
容易踩的坑:
- worker 中 panic 未 recover → 整个程序 crash;必须在每个 goroutine 入口加
defer func(){if r:=recover();r!=nil{...}}() - 把错误和正常结果都往同一个
out chan发 → 消费方无法区分类型;应拆成out chan Result和err chan error两个通道 - 用
len(out)判断结果数量 → 错误,channel 长度不可靠,且 Fan-in 未结束前长度会变;正确做法是用WaitGroup+ 计数器,或从range out收集到 close 为止 - 聚合时忽略错误顺序 —— 如果需要保持输入顺序,不能直接并发写入 slice,得用索引定位或带序号的结果结构
复杂点在于:worker 的生命周期、错误传播路径、结果顺序保障这三者经常互相牵制,一个没对齐就导致数据错乱或死锁。别图省事把错误塞进结果 channel。










