向nil channel收发会永久阻塞,向已关闭channel发送panic,接收已关闭channel在qcount==0时返回零值且ok==false;chansend按c==nil、c.closed!=0、recvq非空、缓冲区等优先级逐层判断阻塞。

向 nil channel 发送或接收,会永久阻塞;向已关闭的 channel 发送会 panic;接收已关闭 channel 时,qcount == 0 才返回零值且 ok == false——这些不是约定,而是 hchan 字段和 chansend/chanrecv 函数里硬编码的判断逻辑。
chansend 函数如何决定是否挂起 goroutine
发送操作不靠“缓冲区有没有空位”单一条件判断阻塞,而是按固定优先级逐层检查:
-
c == nil:直接 panic(阻塞式)或返回false(非阻塞式) -
c.closed != 0:panic("send on closed channel") -
sg := c.recvq.dequeue()非空:跳过缓冲区,把数据从 sender 栈拷贝到 receiver 栈,唤醒sg.g,立即返回true c.qcount :有空位,写入 <code>buf[c.sendx],更新sendx和qcount- 以上都不满足,且
block == true:将当前 goroutine 封装为sudog,入sendq,调用gopark挂起
关键点:recvq 有等待者时,哪怕 qcount == 0(缓冲为空),也绝不会走 buf 路径——这是无缓冲 channel 的本质,也是带缓冲 channel 实现“配对转发”的核心机制。
为什么 len(ch) 和 cap(ch) 不等于 sendx == recvx 或 buf == nil
len(ch) 返回的是 qcount 字段,cap(ch) 返回的是 dataqsiz。这两个值与 sendx/recvx 索引、buf 是否为 nil 完全无关:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
-
sendx == recvx只表示环形缓冲区“逻辑上”可能为空,但实际是否为空,只看qcount == 0 -
buf == nil仅当dataqsiz == 0(即无缓冲 channel)时成立;此时所有通信都走sendq/recvq配对,qcount始终为 0 - 误用
sendx == recvx判断 channel 是否空,会在缓冲区绕满一圈后(sendx回到 0,recvx还没追上)给出错误结论
select 多 case 同时 ready 时,recvq 和 sendq 如何影响执行顺序
runtime 对 select 的 case 是随机轮询的,没有 FIFO 保证。但底层真正触发执行的,是 recvq.first 或 sendq.first 是否非空:
- 若某个
case 对应的 channel 的 <code>recvq.first != nil,说明有 goroutine 在等发,该 case 必然 ready - 若某个
case ch 对应的 channel 的 <code>sendq.first != nil,说明有 goroutine 在等收,该 case 也必然 ready - 即使多个 channel 缓冲区都有数据,只要其中任一
recvq非空,它就可能被选中——因为配对转发比读缓冲区更快、更优先
这意味着:你无法靠缓冲区长度预测 select 的行为,真正起作用的是 goroutine 的等待状态和 runtime 的随机索引选择。
close(ch) 后还能读多少次
关闭 channel 后,closed 字段置 1,但 qcount 不变。因此:
- 只要
qcount > 0,仍能从 <code>buf[recvx]正常读出剩余数据,ok == true - 只有当
qcount == 0时,才立刻返回零值,且 <code>ok == false - 注意:
close不清空buf,也不移动recvx;后续每次接收都会推进recvx并递减qcount
最容易被忽略的是:你不能靠 “channel 已关闭” 推断 “不能再读”,必须结合 qcount 状态;而这个状态,外部代码完全不可见——只能靠 v, ok := 检测。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










