无缓冲 channel 的阻塞是其并发模型的底层契约,要求发送与接收 goroutine 同时就绪才能完成通信,否则任一方立即阻塞;单 goroutine 写入将永久阻塞。

Go 语言中 Channel 的阻塞行为不是“可选特性”,而是其并发模型的底层契约——它直接决定 goroutine 是否挂起、调度器是否介入、程序是否死锁。理解它,关键不在背规则,而在看清「谁在等谁」。
无缓冲 Channel 必须双向就绪,否则立刻阻塞
无缓冲 Channel(make(chan int))本质是同步点:发送方和接收方必须在同一时刻准备好,数据才能传递。这不是“延迟”,而是强制会合。
- 单 goroutine 写
ch → 立即阻塞,因为没人在读,运行时抛出 <code>fatal error: all goroutines are asleep - deadlock! - 单 goroutine 读
→ 同样立即阻塞,因为没人写,同样死锁 - 正确模式一定是:一个 goroutine 发,另一个 goroutine 收,且二者并发执行
有缓冲 Channel 的阻塞只发生在边界上
有缓冲 Channel(make(chan int, 3))像带格子的快递柜:只要格子没满就能塞,只要格子没空就能取。阻塞只发生在两个临界点:
- 写入时缓冲区已满(
qcount == dataqsiz),且无 goroutine 正在读 → 阻塞 - 读取时缓冲区为空(
qcount == 0),且无 goroutine 正在写 → 阻塞 - 缓冲区大小为 0(
make(chan int, 0))等价于无缓冲,不是“非阻塞”
非阻塞操作只能靠 select + default 实现
Go 没有内置的“异步 send/recv”语法。所谓“非阻塞”,实际是用 select 语句试探性尝试,失败就走 default 分支:
select {
case ch
- 这背后调用的是底层
chansend(c, elem, false, ...),block=false表示不挂起 goroutine - 对 nil channel 做非阻塞操作会直接返回 false,不会 panic;但阻塞操作会永久挂起
- 注意:
default不代表“失败”,只是“此刻不可行”,需结合业务逻辑判断是否重试或丢弃
死锁往往源于关闭缺失或方向错配
最常见的死锁不是写错语法,而是控制流断在了 channel 边界上:
-
for range ch会一直等,直到ch被close();忘了 close → 接收方永远卡住 - 向已关闭的 channel 发送(
ch )→ 直接 panic,不是阻塞 - 从已关闭的 channel 接收 → 立即返回零值 +
false,但若没检查ok,可能误判为有效数据 - 多个 goroutine 循环依赖:A 等 B 发,B 等 C 发,C 等 A 发 → 形成等待环,调度器无法唤醒任何一方
真正容易被忽略的,是那些看起来“没写错”的代码——比如 range 循环没配 close,或者 select 里漏了 default 又没设超时,它们不会报错,但会让 goroutine 在后台无声枯等。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











