必须由独立goroutine统一关闭channel:所有生产者通过sync.WaitGroup同步完成后再close(ch),避免panic、数据截断或消费者卡死;消费者用for range ch自动退出。

为什么不能让生产者自己调用 close(ch)
多个生产者共用一个 ch 时,谁关、何时关、关几次,Go 不会协调——第二次 close(ch) 直接 panic: close of closed channel;关早了,数据还没发完就被截断;关晚了,消费者永远卡在 for range ch 里等不到结束信号。单生产者看似可自关,但一旦后续扩展为多生产者,立刻埋雷。真正安全的关闭动作必须是:所有生产者都完成 wg.Done() 后,由一个独立 goroutine 统一执行。
怎么用 sync.WaitGroup + 单独 goroutine 关闭 channel
这是线上最稳妥的组合,解耦了生产逻辑和关闭时机:
- 每个生产者启动前调
wg.Add(1),结束后defer wg.Done() - 立即起一个 goroutine:
go func() { wg.Wait(); close(ch) }()—— 这是唯一合法的close(ch)位置 - 消费者全部用
for val := range ch,channel 关闭后自动退出,无需手动检查ok - 注意:别复用同一个
wg管理消费者,否则wg.Wait()可能提前返回
带缓冲 channel 的大小怎么设才不翻车
缓冲区不是越大越好,它本质是内存暂存区,设太大既掩盖背压问题,又可能 OOM;设太小则退化为同步阻塞,吞吐上不去:
-
make(chan int, 1)和无缓冲行为几乎一致,消费者一卡住,第二个生产者就阻塞 -
make(chan int, 100)是常见起点,适用于每秒几十到几百任务、单次处理约 100ms 的场景 - 经验公式:按「峰值 QPS × 平均处理延迟」估算,例如 50 QPS × 200ms ≈ 缓冲 10–15
-
len(ch)只是快照,别拿它做调度决策(比如“满一半就降速”不可靠)
消费者如何避免漏数据或卡死
漏数据往往不是 channel 本身的问题,而是生命周期管理错位:
- 用
for v := range ch是最简洁安全的写法,但它依赖 channel 被正确关闭;如果生产者 panic 退出没来得及close(ch),消费者就永远挂起 - 真需要超时或主动取消,必须引入
context.Context,在 handler 内部select监听ctx.Done(),而不是只靠range - 不要在同一个
select里对同一个ch同时写和ch ,容易死锁 - 多个消费者共享一个
ch不会重复消费——Go runtime 保证公平分发;重复只发生在业务层没发 ACK 或失败没重试
实际最难的部分不是写对语法,而是把「谁负责关 channel」「关之前是否真完成了」「消费者是否真处理完了最后一条」这三件事,在并发下稳稳对齐。稍有松动,日志里就是满屏 panic 或静默丢任务。











