Go GMP 调度器:从 G/M/P 三色模型到抢占式调度全景
Go GMP 调度器从 G/M/P 三色模型到抢占式调度全景goroutine 看起来轻量背后却是一套精密的 GMP 调度系统。理解 M/P/G 三色模型能让你写出不再看似并发但串行执行的 bug。一、GMP 是什么Ggoroutine用户态协程每个 G 有独立栈Mmachine操作系统线程运行 Go 代码Pprocessor逻辑处理器持有本地运行队列调度器把 G 调度到 M 上执行调度单位是 P。P 数量 GOMAXPROCS默认 CPU 核数。二、调度流程图-------- new G → | global queue | -------- ↓ ------------------- | P (per-CPU) | | local run queue | (256 slots) | current M | ------------------- ↓ M thread ↓ Operating System新建 G 入 local run queuelocal 空了从 global 偷 1/2从其他 P 偷 1/2工作窃取work stealing让负载均衡三、抢占式调度3.1 老问题协作调度Go 1.13 之前的调度是协作的如果某个 G 里跑了死循环scheduler 无法抢占它整个程序卡死。修复1.14 引入基于信号的异步抢占SIGURG。3.2 新版sysmon force preemptruntime 的 sysmon 协程每 10ms 检查P 上 G 运行超过 10ms → 强制暂停系统调用超 20ms → 解绑 P把 P 让给其他 Mruntime.Gosched()// 主动让出四、协程状态机G 状态变化idle刚创建runnable在本地队列runningM 上跑syscall进入系统调用waitingIO/chan/lock 等阻塞dead已结束P 的runq是 FIFO 256 长度环形缓冲溢出转 global 队列。五、GC 与调度并行Go GC 用 STW 三色标记。GC 触发的所有 STW 都通过 P 来合作关闭 P → STW → GC → 重新调度 P大约 6 个时刻 STW单次最多几十微秒六、实战调整 GOMAXPROCSimportruntimeruntime.GOMAXPROCS(runtime.NumCPU())// 默认// 容器里读 cgroup 的 CPU 配额importgo.uber.org/automaxprocsautomaxprocs.Set()// 自动取 cgroup quota云原生环境一定要接automaxprocs否则 CPU quota 变化后进程会跑过载。七、踩坑清单手动调 GOMAXPROCS 反而更慢除非热点在 IO/CPU 混合否则默认最佳goroutine 多未必快所有 G 都要等同一把锁 串行每个 M 都会消耗 OS 线程栈1MBLinux default 8MB ulimit注意runtime/debug.SetMaxStackdebug.Pprof 抓 P/Profile看 P 与 G 调度平衡八、总结与展望GMP 是 Go 高性能的引擎。理解 G/M/P 三色模型能让你写 goroutine 时考虑 bound数量并发量调 runtime 时知道改哪几个变量调试调度问题时用 GODEBUGschedtrace1Go 1.24 实验智能 P 调度算法最大化并行性。九、参考文献The Go Blog: The Go schedulerruntime/trace 文档Go 1.14 release notes: asynchronous preemption