无缓冲 channel 在发送与接收未同时就绪时必然卡住,即要求双方必须在不同 goroutine 中且均处于就绪状态;同一 goroutine 中先发后收会立即死锁,触发 fatal error: all goroutines are asleep - deadlock。

无缓冲 channel 什么时候会卡住你
它不是“慢”,是设计上就要求双方必须同时就绪。只要发送和接收不在两个 goroutine 里,或者某一方提前退出没发/没收,fatal error: all goroutines are asleep - deadlock 几乎必然发生。
- 同一 goroutine 写
ch 然后紧接着 <code> → 必死锁 - 启动了 5 个 worker 向同一个
chan struct{}发信号,但主 goroutine 只收 3 次 → 剩下 2 个永远阻塞 - worker panic 退出前没发信号,主 goroutine 在等它 → 永久 hang 住
这类问题不会报错,也不会超时,程序就停在那里,连 panic 都不触发。调试时得靠日志或 pprof 看 goroutine 状态,而不是查错误信息。
用无缓冲 channel 做同步,为什么别用 chan int
语义不清,还浪费内存。它只传递“发生了”这个事实,不需要带值。
- 推荐用
make(chan struct{}):零大小、零开销、意图明确 - 别用
make(chan bool)或make(chan int):哪怕只传true或1,也多占 1 字节或 8 字节,且容易让人误以为你在传业务数据 - 接收端写成
就够了,不用赋值给变量;如果真要区分多个完成事件,用多个 <code>chan struct{},而不是塞不同整数进去
struct{} 是 Go 并发里最轻量的“握手令牌”,不是语法糖,是设计契约。
有缓冲 channel 不是“更快的无缓冲”,而是行为完全不同
缓冲大小为 1 的 make(chan int, 1) 和无缓冲的 make(chan int) 表现差异极大:
- 向
make(chan int, 1)发一次不会阻塞,哪怕没人收;再发第二次才卡住 - 从空的
make(chan int, 1)接收会阻塞;但从已存一个值的缓冲里接收,立刻返回 -
len(ch)对有缓冲 channel 有意义(当前队列长度),对无缓冲永远是 0 - 关闭后,有缓冲 channel 还能读完剩余数据;无缓冲 channel 关闭后发数据直接 panic,收数据可能死锁
所以别指望把 make(chan T, 1) 当作“防死锁的无缓冲”来用——它改写了通信模型,不再是严格同步,而是带缓存的异步交接。
选哪个,关键看你要不要“解耦生产者和消费者”
无缓冲 channel 的本质是 rendezvous(会合点):两方必须面对面交接,一步到位。有缓冲 channel 的本质是 mailbox(邮箱):发信人投完就走,收信人啥时候看都行。
- 需要确保“对方真的处理完了这个请求”,比如初始化完成、临界区退出、测试中等异步逻辑结束 → 用无缓冲
- 生产者生成快、消费者处理慢,或存在突发流量,或要避免调用方被阻塞 → 用有缓冲,容量按预期峰值设(别盲目设大,内存和延迟会涨)
- 想实现信号量(如限制并发数)、任务队列、日志缓冲 → 有缓冲更自然;想做 goroutine 启动协调、简单互斥、测试同步 → 无缓冲更直接
最容易被忽略的一点:无缓冲 channel 的阻塞是调度器级的,每次收发都涉及 goroutine 切换;而有缓冲 channel 在缓冲未满/未空时,只是内存拷贝。如果你在高频循环里用无缓冲做“伪轮询”,性能会明显掉 —— 那不是 channel 用错了,是模型选错了。











