无缓冲 channel 一发就卡住是因为其要求发送与接收必须同步进行,若无接收方则发送操作永久阻塞;这是设计使然,用于强制 goroutine 间同步协作。

无缓冲 channel 为什么一发就卡住
因为 make(chan int) 创建的是无缓冲 channel,它要求发送和接收必须“同时发生”——没有 goroutine 在另一端等着收,send 就永远阻塞。这不是 bug,是设计:它天然强制同步协作。
常见错误现象:fatal error: all goroutines are asleep - deadlock,尤其新手写完 c 就停住,忘了起 goroutine 去 <code>。
- 必须配对使用:发送前确保有 goroutine 准备好接收(或反过来)
- 不能在同一个 goroutine 里先发后收(除非用 select + default,但那就不是纯同步了)
- 调试时加
fmt.Println很容易误判——print 本身有 IO 开销,可能掩盖竞态时机
goroutine + 无缓冲 channel 实现强同步的典型写法
它最常用于“等某个操作做完再继续”,比如初始化完成通知、任务交接确认。核心逻辑是:一方发,另一方收,发完才往下走。
示例场景:主 goroutine 等待子 goroutine 完成资源加载:
立即学习“go语言免费学习笔记(深入)”;
done := make(chan struct{})
go func() {
loadConfig()
done <- struct{}{} // 加载完才发
}()
<-done // 主 goroutine 这里阻塞,直到收到
// 继续执行依赖 config 的逻辑- 用
struct{}是惯例:零内存占用,语义清晰(只传信号,不传数据) - 别用
chan bool或chan int传“true”“1”——容易让人误以为要读值做判断 - 如果子 goroutine 可能 panic,记得 recover,否则
永远等不到
和带缓冲 channel 的关键区别在哪
缓冲大小为 0 和 1 表面看只差一个数字,行为却完全不同:make(chan int, 0) 等价于 make(chan int),仍是无缓冲;而 make(chan int, 1) 允许发一次不阻塞,哪怕没人收。
- 无缓冲:同步点明确,适合“握手”“等待完成”
- 带缓冲(哪怕 size=1):引入异步窗口,可能掩盖逻辑时序问题,比如本该等 A 完再做 B,结果 B 提前执行了
- 性能上无差别,但语义风险高——size=1 的 channel 容易被当成“安全队列”,实际可能漏掉通知
容易被忽略的关闭陷阱
对无缓冲 channel 调用 close() 后,再 会立刻返回零值,但 <code>c 会 panic:<code>panic: send on closed channel。这和带缓冲 channel 不同——后者 close 后还能读完缓存数据。
- 不要为了“让接收方退出”而随便 close 无缓冲 channel,应该用额外的 done channel 或 context
- 如果真要 close,确保所有发送方都已退出,且接收方用
v, ok := 判断是否关闭 - 多个接收方共用一个无缓冲 channel?别这么干——谁收谁没收完全不可控,同步语义就崩了
真正难的不是写对语法,是想清楚“谁该等谁”“等的是动作完成,还是数据到达”。无缓冲 channel 不提供调度,它只放大你设计里的同步意图。










