百度二面:Go 语言 GMP 模型面试题解析
大家好,我是老周,今天跟大家分享的是一道关于 Go 语言 GMP 模型的面试题。这是一道百度二面的面试真题,核心问题是 “GMP 模型中的 work stealing(工作窃取)机制会偷多少个 G(协程)”。如果不了解 GMP 模型,可能连 “偷多少” 是什么意思都不清楚。下面我们将从 GMP 模型的基础概念开始,逐步拆解这个问题。
一、GMP 模型核心概念
首先要明确 GMP 三个字母分别代表的含义,这是理解后续逻辑的基础:
- G(Goroutine):即 Go 语言中的协程,是轻量级的执行单元,也是我们调度和执行的核心对象。当我们编写
go function()
这样的代码时,本质就是创建了一个 G(协程)。 - M(Machine):对应操作系统的内核线程,是真正执行代码的 “载体”。G 需要绑定到 M 上才能被 CPU 调度执行,也就是说,G 的逻辑最终要通过 M(内核线程)来落地运行。
- P(Processor):即调度器,负责管理 G 的队列并将 G 分配给 M 执行。需要注意的是:P 和 M 是 “一对一绑定” 的关系,一个 P 只能对应一个 M,一个 M 也只能绑定一个 P。我们平时给 Go 应用程序设置 “最大进程数”(实际是GOMAXPROCS参数),本质就是设置 P 的数量 ——P 的数量直接决定了 Go 程序能同时利用的 CPU 核心数(默认等于 CPU 核心数)。
二、G(协程)的入队逻辑
创建 G 之后,需要将其放入队列等待调度,Go 设计了 “本地队列” 和 “全局队列” 两种存储结构,优先级和性能有明显区别:
1. 优先放入本地队列
每个 P(调度器)都有一个专属的本地队列,特点是:
- 无锁设计:因为本地队列只归当前 P 所有,其他 P 或 M 不会访问,不需要加锁保护,性能更高,能避免资源竞争。
- 分配规则:创建 G 时,会优先将 G 放入 “本地队列中 G 数量较少” 的 P 中(即 “哪个 P 的本地队列空 / 数量少,就放哪个”),尽量保证各 P 的任务负载均衡。
2. 本地队列满时,放入全局队列
如果所有 P 的本地队列都已满(达到队列容量上限),则会将 G 放入全局队列,特点是:
- 加锁设计:全局队列是所有 P 共享的,多个 P 访问时会存在资源竞争,必须通过加锁保证线程安全,因此性能比本地队列低。
三、G(协程)的出队与调度逻辑
P 会不断从队列中取出 G,分配给绑定的 M 执行,出队顺序遵循 “本地优先、全局补充、窃取兜底” 的规则:
1. 优先消耗本地队列的 G
P 首先会处理自己本地队列中的 G,按顺序取出 G 并分配给绑定的 M,M 执行 G 的逻辑(本质是调用 G 对应的函数)。
2. 本地队列为空时,从全局队列取 G
如果 P 的本地队列为空,会去全局队列中 “取一批 G”,填充到自己的本地队列,再继续执行调度。
3. 全局队列也为空时,触发 work stealing(工作窃取)机制
如果 P 的本地队列和全局队列都没有 G,但发现其他 P 的本地队列中还有较多 G,就会触发work stealing 机制:
- 核心逻辑:当前 P 会从 “有剩余 G 的其他 P 的本地队列” 中,窃取一半的 G,放入自己的本地队列,之后再从自己的本地队列中取 G 分配给 M 执行。
四、面试题答案
回到百度二面的核心问题 ——“GMP 模型中 work stealing 机制会偷多少个 G?”
答案:当某 P 的本地队列和全局队列都为空时,会通过 work stealing 机制,从其他 P 的本地队列中窃取一半的 G,补充到自己的本地队列中。
以上就是今天的Go 语言 GMP 模型面试题解析分享,希望对大家有所帮助。
#it##计算机##程序员##golang#