无缓冲 channel 阻塞在发送操作上;因无缓冲区,发送必须等待接收方就绪,二者同步完成,故 send 会卡在 ch

无缓冲 channel 阻塞在哪儿?
无缓冲 channel 本身不存数据,发送和接收必须**同时就位**才能完成。这不是“慢”,而是设计上的同步点——send 会卡在 ch 这一行,直到有 goroutine 在同一时刻执行 <code>;反过来也一样。
常见错误现象:fatal error: all goroutines are asleep - deadlock。这几乎一定是因为:只有一个 goroutine 在发(或收),没另一个去配对。
- 别在 main 里直接
ch 后接着 <code> —— 看似“一来一回”,但它们是顺序执行,不是并发就绪 - 想测试?必须用 goroutine 包一层发送或接收,比如
go func() { ch - 如果 sender 和 receiver 都在同一个 goroutine 里,且顺序写死,那必然死锁
什么时候该用无缓冲 channel?
它本质是 goroutine 之间的「握手信号」,不是数据管道。适合需要严格同步的场景,比如等待某个操作真正完成、协调启动时机、实现互斥临界区。
典型使用场景:
- 启动 goroutine 后,等它初始化完毕再继续 —— 用
done := make(chan struct{}),goroutine 结束前 close 或发空值 - 模拟“锁”行为:一个 goroutine 持有 channel(发),其他想进临界区就得先从它那儿收一次
- 在测试中等待异步逻辑结束,避免用
time.Sleep硬等
注意:struct{} 是最常用的无缓冲 channel 类型,因为零内存开销;别用 chan int 来做信号,语义不清还浪费空间。
无缓冲 vs 有缓冲:参数和行为差异
创建时只差一个数字:make(chan int) 是无缓冲,make(chan int, 1) 就是有缓冲、容量为 1。这个数字直接影响阻塞逻辑:
- 无缓冲:发送永远阻塞,直到有人接收;接收也永远阻塞,直到有人发送
- 有缓冲:发送只在缓冲满时阻塞;接收只在缓冲空时阻塞
- 缓冲大小为 0 和不填(即无缓冲)效果相同,但建议显式不写数字,语义更清晰
性能上,无缓冲 channel 的每次操作都涉及 goroutine 切换和调度器介入,比有缓冲略重;但如果你真需要同步,这点开销就是必要成本,不该为了“快”而改用带缓冲的 channel 来模拟同步逻辑。
容易被忽略的关闭和 nil channel 行为
对无缓冲 channel 调用 close(ch) 后,后续接收仍能读到零值(如 0、""、nil),但发送会 panic:panic: send on closed channel。这和有缓冲 channel 表现一致,但更容易踩坑——因为无缓冲场景下,你往往更关注“谁先发/谁先收”,反而忘了关 channel 的时机。
- 别在 sender 里关 channel —— receiver 可能还没开始读,关了就 panic
- receiver 关 channel 是危险操作,sender 通常不知道 channel 已关
- 最安全的做法:由创建者控制生命周期,或用额外的
donechannel 协调退出 - 向
nilchannel 发送或接收,会永远阻塞(不是 panic),这是调试时很难发现的静默问题
无缓冲 channel 的难点不在语法,而在对“同步时机”的直觉判断——多一 goroutine,少一 close,错一个顺序,就可能卡住或 panic。写的时候得盯着控制流,而不是只看 channel 声明。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











