无缓冲channel卡死是因为发送和接收必须跨goroutine严格同步:make(chan int)创建的通道容量为0,发送方会阻塞直到有接收方就绪,反之亦然;同goroutine中收发必然死锁。

为什么直接用 make(chan int) 会卡死
因为无缓冲 channel 要求发送和接收严格同步:生产者一执行 ch 就阻塞,直到有消费者在另一端执行 。如果消费者还没启动,或启动慢了,生产者就永远停住,最终触发 fatal error: all goroutines are asleep - deadlock。
- 必须显式指定缓冲区大小,比如
make(chan int, 10),让生产者能先存几条再等消费 - 生产者和消费者都得用
go启动,否则串行执行失去并发意义 - 哪怕只用一个生产者,也别省略
go—— 否则 main 协程发完就退出,消费者根本没机会跑
多生产者时谁来调用 close(ch)
不能由任意一个生产者自己关。第二次 close(ch) 会 panic:panic: close of closed channel;关早了,后面还有数据没发完就被截断;关晚了,消费者永远卡在 for range ch 里等不到结束信号。
- 正确做法是起一个独立 goroutine:
go func() { wg.Wait(); close(ch) }() - 每个生产者启动前调
wg.Add(1),结束后defer wg.Done() - 这个 goroutine 是唯一合法的
close(ch)位置,它确保所有生产者真正完成后再关 - 千万别复用同一个
sync.WaitGroup管理消费者,否则wg.Wait()可能提前返回
消费者怎么写才不漏数据、不卡死
for val := range ch 是最简洁安全的写法,但它完全依赖 channel 被正确关闭。如果生产者 panic 退出、没走到 close(ch),消费者就永久挂起。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 必须搭配
sync.WaitGroup+ 独立 goroutine 关 channel,否则range没意义 - 真需要超时或主动取消,必须引入
context.Context,在 handler 内部select监听ctx.Done(),不能只靠range - 别在同一个
select里对同一个ch同时写和读,容易死锁 - 多个消费者共享一个
ch不会重复消费——Go runtime 保证公平分发;重复只发生在业务层没发 ACK 或失败没重试
缓冲区大小设多少才不翻车
缓冲区不是越大越好。它本质是内存暂存区:make(chan int, 1000) 可能掩盖背压问题,导致 OOM;make(chan int, 1) 和无缓冲行为几乎一致,消费者一卡,第二个生产者就阻塞。
- 经验公式:按「峰值 QPS × 平均处理延迟」估算,例如 50 QPS × 200ms ≈ 缓冲 10–15
-
len(ch)只是快照,别拿它做调度决策(比如“满一半就降速”不可靠) - 如果消费者含阻塞 I/O(如 HTTP 请求),缓冲区再大也救不了吞吐,此时应优先优化消费逻辑本身
最麻烦的从来不是语法怎么写,而是三件事在并发下严丝合缝地对齐:谁负责关 channel、关之前是否真完成了、消费者是否真处理完了最后一条。松动一点,线上就是静默丢任务或满屏 panic。










