问题核心:
Goroutine 被阻塞(如 channel、锁、cond 等)
Goroutine 进入系统调用(如读文件、网络请求等)
那么它 在变为不可运行(waiting
)或从 running
变为 runnable
之后,到底放在哪儿?
这种场景通常是 G 主动等待某个资源,比如:
<-ch
读 channel
sync.Mutex.Lock()
sync.Cond.Wait()
当前 G 会从 running
→ waiting
状态。
它不会出现在调度队列中。
会挂到某个数据结构上,例如:
channel 的等待队列(sudog
结构)
Mutex 的等待队列(链表)
✅ 它不会被放入 P 的 runq,也不会在全局队列中。
状态:waiting
→ runnable
唤醒函数会调用 ready()
函数把它放入调度队列。
放置位置:当前 P 的本地队列(优先),否则全局队列
read()
, select()
)G
进行系统调用,会进入 syscall
状态。
当前绑定的 M
也被带走去执行系统调用。
P
被解绑,留给别的 M
来复用,保证并发度。
这时候:
G
→ syscall
(不在调度队列中)
M
→ syscall
P
→ 可被抢占、绑定别的 M
Go 会检查:
如果有空闲 P
,就立即执行它(通过 injectglist()
机制)
否则将该 G
放入 全局 run queue
放置位置:优先尝试立即执行,否则放入 全局队列
情况 | 状态变化 | 暂时位置 | 被唤醒后放置位置 |
---|---|---|---|
阻塞(channel, lock 等) | running → waiting |
资源的等待队列(如 sudog 链) | 当前 P 的本地队列 或全局队列 |
系统调用 | running → syscall |
不在 runq,中断当前调度关系 | 全局队列 或 直接调度执行 |