[典藏版] Golang 调度器 GMP 原理与调度全分析
Go 调度本质是把大量 goroutine 分配到少量线程上去执行,并利用多核并行
- G:
goroutine
用户态协程
- M: thread 内核态线程
- P: processor 处理器 包含运行
goroutine
的资源
如果线程想运行 goroutine
必须先获取 P,P 中包含可运行的 G 队列
- 全局队列 Global Queue: 存放等待运行的 G
- P 本地队列:类似 GQ,存的数量有限 ≤ 256,新建 G' 优先加入到 P 本地队列,如果满了,则把本地队列中一半 G 移动到全局队列
- P 列表:所有 P 在程序启动时创建,保存在数组,最多 GOMAXPROCS 个
- M:线程想运行任务就得获取 P,从 P 的本地队列获取 G,P 队列为空时,M 也会尝试从全局队列拿一批 G 放到 P 的本地队列,或从其他 P 的本地队列偷一半放到自己 P 的本地队列。M 运行 G,G 执行之后,M 会从 P 获取下一个 G,不断重复下去。
在 Go 中,线程是运行 goroutine
的实体,调度器的功能是把可运行的 goroutine
分配到工作线程上。Goroutine 调度器和 OS 调度器是通过 M 结合起来的,每个 M 都代表了 1 个内核线程,OS 调度器负责把内核线程分配到 CPU 的核上执行

M 与 P 的数量没有绝对关系,一个 M 阻塞,P 就会去创建或者切换另一个 M,所以,即使 P 的默认数量是 1,也有可能会创建很多个 M 出来。
调度器设计策略
线程复用:避免频繁创建、销毁线程
- work stealing 机制:当本线程无可运行 G,尝试从其他线程绑定的 P 偷 G,而不是销毁线程
- hand off 机制: 当本线程因为 G 进行系统调用阻塞时,线程释放绑定的 P,把 P 转移给其他空闲的线程执行
go func 调度