如何理解 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)
才能从仓库拿任务干活
相关推荐
格砸44 分钟前
从入门到辞职|从ChatGPT到OpenClaw,跟上智能时代的进化
前端·人工智能·后端
蝎子莱莱爱打怪1 小时前
GitLab CI/CD + Docker Registry + K8s 部署完整实战指南
后端·docker·kubernetes
哈密瓜的眉毛美2 小时前
零基础学Java|第三篇:DOS 命令、转义字符、注释与代码规范
后端
用户60572374873082 小时前
AI 编码助手的规范驱动开发 - OpenSpec 初探
前端·后端·程序员
哈密瓜的眉毛美2 小时前
零基础学Java|第二篇:Java 核心机制与第一个程序:从 JVM 到 Hello World
后端
用户8307196840822 小时前
RabbitMQ vs RocketMQ 事务大对决:一个在“裸奔”,一个在“开挂”?
后端·rabbitmq·rocketmq
初次攀爬者2 小时前
RocketMQ 集群介绍
后端·消息队列·rocketmq
Leo8993 小时前
go 从零单排 之 一小时通关
后端
花花无缺3 小时前
搞懂@Autowired 与@Resuorce
java·spring boot·后端
CodeMonkey3 小时前
记一次傻逼一样的 OOM 异常
后端