扇出是将一个输入源数据分发给多个goroutine并行处理,需用闭包封装每个goroutine的读取逻辑以避免竞争和漏数据;扇入是合并多个输出channel,必须用闭包为每个channel绑定专属goroutine确保不丢数据。

什么是扇出(fan-out)?Go里怎么用闭包做并发分发
扇出就是把一个输入源的数据,分发给多个 goroutine 并行处理。关键不是“起多个 goroutine”,而是让每个 goroutine 独立消费同一份输入流,且不互相阻塞或漏数据。
常见错误是直接把同一个 chan int 传给多个函数——这会导致只有一个 goroutine 能读到值,其余阻塞在 上,根本没并发。
正确做法是用闭包封装每个 goroutine 的读取逻辑,让每个实例持有自己的读取循环:
func fanOut(in <p>⚠️容易踩的坑:<br>
- 不要用 <code>for range in</code> 在多个 goroutine 里直接读——<code>range</code> 会尝试读完所有值,但 channel 只能被一个 goroutine 消费完<br>
- 闭包捕获变量时别写 <code>go func() { ... }(ch)</code> 而漏掉参数传入,否则所有 goroutine 共享最后一个 <code>ch</code> 的值<br>
- 缓冲区大小设太小(比如 <code>make(chan int, 1)</code>)会让扇出侧频繁阻塞,拖慢整体吞吐</p><h3>扇入(fan-in)为什么必须用闭包?单纯 select 会丢数据</h3><p>扇入是把多个输出 channel 合并成一个。如果不用闭包隔离每个输入源的读取逻辑,<code>select</code> 在多路复用时会随机选择就绪的 case,导致某个 channel 被反复读、另一个长期饿死——这不是合并,是竞态漏读。</p><p>闭包的作用是为每个输入 channel 绑定专属的 goroutine,确保它持续读取直到关闭:</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/gongju/2525" title="Go语言(Golang)1.26.0"><img
src="https://img.php.cn/upload/manual/001/589/237/6a6adeed24a4a355.png" alt="Go语言(Golang)1.26.0" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/gongju/2525" title="Go语言(Golang)1.26.0" class="overflowclass">Go语言(Golang)1.26.0</a>
<p class="overflowclass">Go语言(Golang)1.26.0版本官方下载,版本号 1.26.0,适合旧项目维护、兼容性测试和指定版本开发环境搭建。</p>
</div>
<a rel="nofollow" href="/xiazai/gongju/2525" title="Go语言(Golang)1.26.0" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div><pre class="brush:php;toolbar:false;">func fanIn(chs ...<p>说明:<br>
- 每个 <code>go func(c 都独占一个 goroutine,不会相互干扰<br>
- <code>out</code> 不能带缓冲(除非你明确要背压),否则可能掩盖下游消费慢的问题<br>
- 如果输入 channel 数量动态变化,别用固定长度的 <code>chs ...,改用切片传参 + <code>len(chs)</code> 控制循环</code></code></p><h3>闭包里传 channel 还是传值?看场景选参数类型</h3><p>闭包参数类型决定并发行为边界。传 <code> 表示只读;传 <code>chan 表示只写;传 <code>chan int</code> 表示可读可写——但后者破坏了类型安全,容易引发 panic。</code></code></p><p>典型组合:<br>
- 扇出侧闭包接收 <code>(输入源)和 <code>chan(输出目标)<br>
- 扇入侧闭包只接收 <code>(每个输入通道)<br>
- 如果闭包内要关闭 channel,必须传 <code>chan int</code>(且仅限该 goroutine 创建的 channel)</code></code></code></p><p>性能影响:<br>
- 传 channel 指针开销极小,远小于复制大量数据<br>
- 但传错方向(比如把 <code>chan 当 <code> 用)会在编译时报错,反而是好事</code></code></p><h3>真实场景下,扇入扇出要配合 context 控制生命周期</h3><p>生产代码里,没人等所有 goroutine 自然结束。比如一个扇出的 worker 因网络超时卡住,整个扇入 channel 就永远发不完。</p><p>必须用 <code>context.Context</code> 注入取消信号,且闭包里要监听 <code>ctx.Done()</code>:</p><pre class="brush:php;toolbar:false;">func worker(ctx context.Context, in <p>注意点:<br>
- <code>ctx.Done()</code> 要在每层 select 里显式检查,不能只在最外层判断<br>
- 不要用 <code>time.Sleep</code> 模拟耗时操作——它不响应 cancel,会绕过 context 控制<br>
- 扇入侧的 goroutine 也要在 <code>for v := range c</code> 前加 <code>select { case ,否则可能泄漏</code></p><p>复杂点在于,扇出/扇入嵌套越深,context 传递和 cancel 时机越难对齐。最容易被忽略的是:某个中间 channel 关闭后,上游还在往里塞数据,而下游已退出——这时得靠 buffer 或 select default 分流,而不是指望闭包自动处理。</p>golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










