GMP 调度模型
G – Goroutine
- 协程(用户级线程)
- 由
go func()产生 - 每个 G 在用户空间中都有独立的栈(2KB,可以动态扩容缩容)
- G0:每次启动一个 M 创建的第一个 G,仅用于调度 G
M – Machine
- 直接映射到操作系统内核线程
- G 得以在 CPU 上执行的载体
- M 的最大数量为 10000
- M0:Go 程序启动后创建的第一个 M
P – Processor
- 维护一个长度为 256 的 LRQ(Local Run Queue),可以通过 CAS 无锁访问,M 优先从自己关联的 P 的 LRQ 中获取 G,大大减少了从 GRQ(Global Run Queue)中获取 G 的锁竞争
- P 的数量等于
GOMAXPROCS(一般设置为 CPU 核心数),限制了真正并行的 M 的数量
调度循环

系统调用
非阻塞式系统调用(chan 操作、网络 I/O)
当进行 chan 操作或网络 I/O 时,G 会和 MP 分离,被挂到 Netpoller 上,M 会进行下一次调度

当 chan 操作或网络 I/O 完成后,G 被唤醒,
1. 有自旋 M:G 被放入自旋 M 关联的 P 的 LRQ 中

2. 有空闲 P:唤醒一个休眠的 M 或创建一个新的 M 来关联空闲 P

3. 无空闲 P:G 被放入 GRQ 中

阻塞式系统调用(文件 I/O)
GM 会和 P 分离,G 和 M 一起进入挂起状态,如果 P 中仍有可运行的 G,会唤醒一个休眠的 M 或创建一个新的 M 来关联 P(Hand Off)

当阻塞式系统调用完成,M 会尝试关联一个空闲的 P(优先关联原来的 P)以执行 G,如果没有空闲的 P,G 被放入 GRQ,M 休眠
抢占机制
系统线程 sysmon:独立于 Go 调度器运行,每 20us~10ms 启动一次,监控所有正在运行的 G,一旦发现某个 G 的运行时间超过了 10 ms 的阈值,sysmon 就会认为它需要被抢占
协作式抢占
sysmon 把“超时” G 标记为“可抢占”,“超时” G 会在抢占点(函数调用、系统调用、GC等)检查“可抢占”标记,主动让出 M
基于信号的异步抢占
sysmon 向“超时” G 所在 M 发送一个 SIGURG 信号,“超时” G 被抢占


