如何理解 Go 的调度模型,以及 G / M / P 各自的职责

一、为什么需要调度模型?

先问一个问题:

Go 里可以同时开成千上万个 goroutine,它是怎么做到的?

如果每个 goroutine 都对应一个操作系统线程,那系统早就炸了。

因为:

  • 线程很重(几 MB 栈空间)
  • 创建销毁成本高
  • 线程切换开销大

所以 Go 自己实现了一套"用户级调度器"。


二、Go 的整体调度模型(GMP 模型)

Go 使用的是:

G - M - P 调度模型

可以理解为:

复制代码
G(任务)
M(干活的工人)
P(调度中心 / 工作许可证)

一个简单类比:

现实世界 Go 中
任务 G (goroutine)
工人 M (machine 线程)
工头 / 任务队列管理 P (processor)

三、G / M / P 分别是什么?


1️⃣ G ------ Goroutine(真正的任务)

G 就是:

你写的每一个 goroutine

例如:

go 复制代码
go func() {
    fmt.Println("hello")
}()

这就是创建了一个 G。

G 里面包含什么?

  • 要执行的函数
  • 栈空间(初始 2KB,可自动扩容)
  • 程序计数器
  • 状态(运行中、等待中等)

G 是"轻量级线程"。


2️⃣ M ------ Machine(真正干活的线程)

M 是:

操作系统线程

它是真正执行代码的"工人"。

⚠️ 注意:

  • M 是系统线程
  • 是 OS 创建的
  • 真正跑 CPU 的是 M

3️⃣ P ------ Processor(最关键)

P 是整个模型的核心。

P 是:

调度器的资源控制单元

它负责:

  • 管理本地 G 队列
  • 把 G 分配给 M 执行
  • 维护调度状态

可以理解为:

M 想干活,必须拿到 P


四、三者关系图

一个简化关系:

复制代码
G → 被放入 → P 的本地队列
M → 必须绑定 → P
M + P → 才能执行 G

执行流程:

复制代码
1. 创建 G
2. G 放入某个 P 的队列
3. M 绑定 P
4. M 从 P 队列取 G
5. 执行 G

五、为什么要有 P?

很多人问:

为什么不直接 G + M?

因为:

如果只有 G 和 M:

  • 所有 goroutine 共享一个全局队列
  • 频繁加锁
  • 性能差

加入 P 之后:

每个 P 有:

  • 本地 G 队列(减少锁竞争)
  • 运行中的 G

优点:

  • 降低锁竞争
  • 提高 CPU 利用率
  • 更好扩展到多核

六、数量关系

默认情况下:

复制代码
P 的数量 = CPU 核心数

可以通过设置:

go 复制代码
runtime.GOMAXPROCS(n)

来修改 P 的数量。


七、完整调度过程(一步一步)

假设有 4 核 CPU:

复制代码
P0  P1  P2  P3

1️⃣ 创建 goroutine

→ 放进某个 P 的本地队列

2️⃣ M 绑定 P

→ 每个 P 同时只能被一个 M 使用

3️⃣ M 从 P 队列取 G

→ 执行

4️⃣ 如果队列空了?

→ 去别的 P 偷任务(Work Stealing)


八、Work Stealing(工作窃取)

这是 Go 调度器非常重要的机制。

如果:

复制代码
P0 很忙
P1 没活干

P1 会:

从 P0 的队列"偷一半任务"过来

这样可以:

  • 保证负载均衡
  • 提高 CPU 利用率

九、阻塞时会发生什么?

比如:

go 复制代码
time.Sleep()
网络 IO
channel 阻塞

当 G 阻塞时:

  1. M 不会一直等
  2. M 会解绑当前 P
  3. P 会找别的 M 继续干活

这样不会浪费 CPU。


十、总结一句话理解 GMP

可以记住这个公式:

复制代码
G 是任务
M 是执行者
P 是调度资源
M 必须拿到 P 才能执行 G

再简化:

复制代码
G = 干什么
M = 谁干
P = 批准干 + 排队系统

十一、面试级总结版本

如果你以后面试,可以这样回答:

Go 使用 GMP 调度模型。

G 表示 goroutine,是待执行的任务。

M 是操作系统线程,真正执行代码。

P 是调度资源,维护本地 goroutine 队列。

M 必须绑定 P 才能执行 G。

Go 通过本地队列 + 工作窃取算法,实现高效并发调度。


十二、给编程小白的最终理解图

你可以在脑中想象:

复制代码
CPU核心数量 = P数量
每个P都有一个任务仓库
M是工人
G是任务
工人必须拿到一个仓库许可(P)
才能从仓库拿任务干活
相关推荐
点心的游戏开发世界5 小时前
GDScript 入门笔记(十一):Lambda 与匿名函数
开发语言·笔记·游戏引擎·godot
摇滚侠5 小时前
《SpringBoot 3:入门与应用实战》第 12 章 JDBC 与事务 使用 JdbcTemplate 阅读笔记 32
spring boot·笔记·后端
步行cgn6 小时前
@Configuration 详解:Spring 配置类的核心注解
java·后端·spring
事圆则缓7 小时前
Kotlin 协程完全指南
开发语言·kotlin
我爱写代码i7 小时前
BeLink - 支持生成多种URL 缩短网址PHP源码
开发语言·php·短网址php源码
Java后端的Ai之路7 小时前
20、Python - 备忘录模式
开发语言·人工智能·python·外观模式·备忘录模式
桦说编程8 小时前
【AtomicAgent系列1】变异测试——过去做不起,现在 agent 做得起
后端·ai编程·vibecoding
统计学小王子8 小时前
数学建模国赛倒计时4天——《统计模型精讲(混合效应模型R语言实现)》
开发语言·数学建模·r语言
知无不研9 小时前
c语言中循环的介绍与简单应用
c语言·开发语言·算法·循环·for·while
郝学胜-神的一滴9 小时前
Qt 高级编程 045:坐标体系深度实战
开发语言·c++·windows·python·qt·程序人生