直接回答:runtime 中 channel 是 hchan 结构体:环形缓冲区 buf(带 sendx/recvx 两个游标)、发送等待队列 sendq、接收等待队列 recvq(都是 sudog 链表,sudog 包装一个挂起的 goroutine)、一把互斥锁及元素元信息。无缓冲 channel 等价于 buf 容量为 0,发送必须与接收直接交接。
展开解析:发送流程(chansend):先看 recvq 是否有等待的接收者——有则跳过缓冲区,直接把值拷贝给对方栈并 goready 唤醒它(这是无缓冲 channel 的核心路径,省一次入队出队);否则缓冲区有空位就写入 buf;都没戏就把当前 goroutine 包装成 sudog 挂进 sendq,gopark 休眠让出 M。接收流程(chanrecv)对称:sendq 有人等则分两种——无缓冲时直接从发送者手里接值,有缓冲时先取 buf 队头再把发送者的值补进 buf(保持 FIFO),随后唤醒发送者;否则从 buf 读或挂入 recvq。close 操作把 recvq 与 sendq 全部唤醒:接收者拿到零值加 ok=false,发送者直接 panic(向已关闭 channel 发送)。select 的实现是把 goroutine 同时挂进多个 channel 的等待队列,任一到货后从其余队列摘除。理解这套结构能解释很多行为:为何 len(chan) 只是快照几乎无用、为何 nil channel 收发永久阻塞(挂起队列都没有,直接 park)。
追问方向:sudog 为什么要缓存复用?select 的 pollorder 随机化防什么?
(约 490 字)