无缓冲 channel 一发就阻塞,因其底层 hchan 的 dataqsiz == 0,无缓冲区存数据,发送操作需等待接收方就绪才能完成同步。

Go channel 阻塞不是“卡住”,而是运行时主动挂起 goroutine 并排队等待——本质是同步协调,不是资源争抢。
无缓冲 channel 为什么一发就阻塞?
因为它的底层 hchan 结构中 dataqsiz == 0,没有缓冲区存数据。发送操作(c )会立即检查是否有 goroutine 在 recvq 等待接收;如果没有,当前 goroutine 就被封装成 sudog 加入 sendq 队列,并调用 gopark 挂起自己。
- 接收方同理:
会先查sendq,空则入recvq并挂起 - 这种“双方必须同时就绪”就是同步语义的来源,也是 Go “通过通信共享内存” 的落地机制
- 常见错误:在单个 goroutine 里先
c 再→ 直接死锁,因为没有另一个 goroutine 参与协作
有缓冲 channel 的阻塞边界在哪?
阻塞只发生在缓冲区满或空时,和无缓冲 channel 的“永远同步”不同,它提供有限异步能力。关键看 qcount 和 dataqsiz 的实时关系:
- 发送阻塞条件:
qcount == dataqsiz(已满),且recvq为空(没人等着收) - 接收阻塞条件:
qcount == 0(已空),且sendq为空(没人等着发) - 注意:
sendx和recvx是环形索引,满/空判断不依赖位置差,而依赖计数器qcount,所以不会因绕圈错判
select 怎么打破阻塞?default 和 timeout 是怎么工作的?
select 不是“轮询”,而是运行时一次性检查所有 case 的 channel 状态。如果所有 channel 都不可读/不可写,且没有 default,整个 select 就阻塞——此时当前 goroutine 被挂起,等任一 channel 就绪后由运行时唤醒。
立即学习“go语言免费学习笔记(深入)”;
- 加
default:→ 非阻塞:任一 case 不可执行时立刻走 default,不挂起 - 用
time.After()做超时:case → 底层是向一个定时 channel 发信号,属于标准 channel 协作,无需额外线程 - 陷阱:多个可执行 case 时,
select随机选一个,不保证 FIFO;别依赖执行顺序
真正容易被忽略的是:channel 阻塞的“代价”几乎为零——goroutine 被挂起后不占 CPU,也不占栈空间(会收缩),但若大量 goroutine 堆在 sendq/recvq 里长期等待,说明设计上存在协作失衡,比如消费者太慢、生产者没节制、或缺少退出信号机制。










