不能让多个生产者自己 close channel,因为关闭操作不可重入,并发关闭或向已关闭channel发送数据均会panic;必须由主goroutine用sync.WaitGroup统一协调关闭时机。

多生产者场景下,仅靠 close(ch) + for range ch 无法安全同步任务完成,必然导致 panic 或漏收数据;真正可落地的信号同步方案,必须由主 goroutine 统一协调关闭时机,且需配合 sync.WaitGroup。
为什么不能让多个生产者自己 close channel
channel 关闭是单次、不可逆操作,多个 goroutine 并发调用 close(ch) 会直接触发 panic:panic: close of closed channel。更危险的是,若某个生产者提前关闭,其余生产者继续向已关闭 channel 发送数据,也会 panic:send on closed channel。
- 无缓冲 channel 上,发送阻塞直到接收就绪;但一旦被意外关闭,所有未完成的发送都会立即崩溃
- 有缓冲 channel 同样不免疫——关闭后仍允许读取剩余值,但任何写入都非法
- 即使加了
recover,也无法恢复 channel 状态,后续逻辑仍不可控
WaitGroup + close 是唯一可控的终局性关闭方案
核心逻辑是:把“谁来关”和“何时关”从生产者手中剥离,交由主 goroutine 全权负责。它不关心每个生产者内部怎么跑,只确保它们全部退出后再执行一次 close(ch)。
- 主 goroutine 调用
wg.Add(n)声明要等 n 个生产者 - 每个生产者在
defer wg.Done()或显式调用wg.Done()保证退出时通知 - 主 goroutine 执行
wg.Wait()阻塞等待全部完成,之后close(ch) - 消费者可放心使用
for v := range ch,因为关闭动作是单点、原子、终局性的
用 struct{} 通道做纯信号同步时,缓冲大小没意义
当 channel 仅用于传递“完成”“就绪”“取消”这类无数据信号(即类型为 chan struct{}),缓冲区大小不影响同步语义,只影响积压能力与内存占用。
-
make(chan struct{}, 0)和make(chan struct{}, 1)在单次信号场景下行为一致:前者要求接收方就绪才发,后者允许发一次不阻塞 - 若信号可能重复(如心跳、重试通知),缓冲为 1 更鲁棒;但若只是“干完了”,用无缓冲更清晰表达同步意图
- 缓冲 > 1 容易掩盖逻辑缺陷——比如误以为“发了三次信号=三次确认”,实际只是队列里堆着三个没人读的
struct{}
超时与取消必须外挂 context,不能依赖 channel 自身
channel 本身没有生命周期管理能力。一旦消费者卡在 ,而生产者因异常未发信号,就会永久阻塞。
- 正确做法是用
select包裹接收逻辑:select { case - 主 goroutine 应通过
context.WithTimeout或context.WithCancel控制整体时限 - 不要在 worker 内部直接监听
ctx.Done()后就退出——需先确保已发送的信号能被接收方看到,否则信号丢失
真正难的不是写对那几行 wg.Wait() 和 close(),而是识别出哪些 goroutine 属于“应被 WaitGroup 覆盖的生产者”,哪些只是临时派生、不该参与终局判断;漏掉一个,range 就永远卡住。



















