无缓冲channel必须由两个goroutine分别收发,否则死锁;其本质是同步点而非数据管道,因发送和接收需严格同时就绪才能完成交换。

make(chan T) 创建的无缓冲 channel 必须由两个 goroutine 分别执行发送和接收,否则必然死锁。它不是用来“传数据”的管道,而是用来“卡住执行流”的同步点。
为什么 ch 会卡死?
因为无缓冲 channel 没有内部存储,ch 这一行会一直阻塞,直到另一个 goroutine 正在执行 <code>。如果只在一个 goroutine 里顺序写这两句,就等于要求自己同时“伸手送”又“伸手接”——不可能完成。
- 常见错误现象:
fatal error: all goroutines are asleep - deadlock - 典型误写:
ch := make(chan int) ch <- 1 // 卡在这儿,永远等不到接收方 <-ch // 根本执行不到
- 正确做法:至少一方进 goroutine,比如:
ch := make(chan int) go func() { ch <- 1 }() // 发送放后台 val := <-ch // 主线程接收
struct{} 为什么是无缓冲 channel 的默认类型?
用 chan struct{} 做信号通道,语义清晰、零内存开销。别用 chan int 或 chan bool 传“真假”或“完成”,那只是占位,还浪费空间。
-
struct{}占 0 字节,len(ch)和cap(ch)都是 0,符合“只同步、不传数据”的定位 - 发送信号:
ch ;接收信号:<code>(不关心值) - 关闭后接收会 panic:
close(ch)后再是死锁,<code>ch 是 panic
什么时候该用无缓冲 channel,而不是 sync.WaitGroup 或带缓冲 channel?
当你需要严格配对的控制流同步,且希望错误尽早暴露(比如漏发信号直接死锁,比静默超时更易调试)。
立即学习“go语言免费学习笔记(深入)”;
- 适用场景:
- 启动 goroutine 后等待其初始化完成(
done := make(chan struct{})) - 实现简单互斥:一个 goroutine 持有 channel 发送权,其他必须先
才能进入临界区 - 测试中等待异步逻辑结束,避免
time.Sleep
- 启动 goroutine 后等待其初始化完成(
- 不适用场景:
- 批量任务分发(用
make(chan job, 10)) - 需要非阻塞尝试(
select+default对无缓冲 channel 仍可能阻塞在 case 上) - 想复用同一个 channel 多次通知(应改用
sync.Once或重置 channel)
- 批量任务分发(用
无缓冲 channel 的阻塞不是 bug,是设计契约
它的每次收发都触发 goroutine 切换和调度器介入,性能略低于有缓冲 channel,但这开销换来的是可推理的协作时序。你看到的“卡住”,其实是两个 goroutine 在通道上完成了一次握手——发送者确认接收者已就绪,接收者确认发送者已交付。这种确定性,在复杂并发协调中比“快一点”重要得多。


















