Go 的 GMP 调度模型与协程如何工作?
未标记
考查重点:G、M、P 分工与任务调度。G是goroutine,M是系统线程,P提供执行Go代码的运行资源;M持有P运行G,通过本地队列、全局队列和窃取分配工作
本题目录
参考答案
核心回答
Go 的 goroutine 是由运行时管理的有栈并发执行单元。大量 goroutine 可以复用一组操作系统线程执行,这套多对多调度通过 G、M、P 三个角色配合完成。以下按 Go 1.27.1 的运行时实现说明。
- G(
goroutine):被调度的任务,包含自己的栈、执行现场和运行状态。创建goroutine后,它先成为可运行任务,何时执行由调度器决定。 - M(machine):操作系统线程,真正承载指令执行;操作系统负责把这些线程调度到 CPU 上。
- P(processor):执行普通
Go代码所需的资源和许可,持有本地运行队列、内存分配相关状态等。M 需要取得 P,才能执行这类Go代码。P 是运行时抽象,不是一个物理 CPU 核。
调度器不断寻找可运行的 G,交给持有 P 的 M 执行。G 结束、等待、主动让出或被抢占后,再选择其他 G。就绪队列分为 P 的本地队列和全局队列;本地找不到工作时,还会检查全局工作、网络就绪事件,并尝试从其他 P 窃取工作。
GOMAXPROCS 对应 P 的数量,控制同时执行 Go 代码的并行度。它既不限制 goroutine 总数,也不等于进程中的操作系统线程总数。
进一步解释
本地队列不空和为空时,分别怎么找 G?
常见路径优先从当前 P 的本地待运行任务中取 G,包含 runnext 和本地队列。但为了公平,调度器也会周期性检查全局运行队列,避免本地任务不断相互唤醒,让全局任务一直等不到执行。
本地没有任务时,可以从全局队列批量取任务、获取网络轮询器发现的就绪 G,或从其他 P 窃取一批任务。真正找不到工作时,线程才可能休眠,等待后续唤醒。
实际查找还穿插计时器、GC 等运行时工作,并非只有一条永远不变的“本地、全局、窃取”流程。理解队列局部性、负载均衡和公平性,比记某个版本的队列容量或固定检查次数更有用。
一个 G 阻塞,会不会连线程也一起卡住?
要看阻塞方式。
对于运行时能管理的 channel 等待,以及可轮询的网络 I/O,通常可以把等待的 G 挂起,让 M 继续执行其他 G。例如 G1 等待 socket 数据时,运行时可以先挂起 G1,让这个执行资源运行 G2;数据就绪后,再把 G1 变成可运行状态,等待调度。
如果进入真正阻塞的系统调用或某些 cgo 调用,M 本身也可能被阻塞。运行时会按相应路径交出或回收 P,让其他 M 继续使用这份执行资源;返回 Go 代码前,需要重新取得 P。因此,存在较多阻塞线程时,M 的总数可以超过 P 的数量。不能把所有 I/O 都概括成“只阻塞 G,绝不占线程”。
goroutine 是否只能主动让出?
不是。Go 除了在等待、函数入口等位置进行协作式调度,也支持异步抢占,防止某个长时间运行的 G 一直占用执行资源。抢占仍受平台和安全点等条件约束,不代表能在任意一条指令处随意暂停。
goroutine 的栈起步较小,并可按需增长和收缩,这有助于承载较多并发任务。但它仍消耗栈、运行时元数据及任务自身资源,也会带来调度和 GC 成本。
常见追问
为什么引入 P,不能直接让线程从全局队列取任务吗?
可以实现,但所有线程频繁争抢同一个全局队列,容易形成竞争。P 把常用队列和局部资源分散开,同时让这些资源能够在线程阻塞时换给其他线程使用,兼顾局部性和负载均衡。
GOMAXPROCS 的默认值一定等于机器 CPU 核数吗?
不能一概而论。Go 1.25 起引入容器感知的默认行为;在这里核对的 1.27.1 中,默认选择会综合逻辑 CPU 数、CPU affinity,以及 Linux cgroup CPU 配额,并可随环境变化更新。显式设置、程序兼容版本与相关运行时开关还会影响这一行为,不能只看宿主机核数。
goroutine 很轻,是否可以每来一个任务就无限创建?
不应只依赖调度器控制业务压力。可以使用有界 worker 池、任务队列,或在创建 goroutine 前取得并发配额;同时安排超时、取消和退出路径。否则即使 CPU 并行度受限,大量等待任务仍可能耗尽内存、连接或下游容量。多个 goroutine 共享进程内存,访问共享状态也仍需要同步。
参考资料
- 国内:Draveness:Go 语言调度器与 Goroutine 实现原理:采用调度器演进、引入 P 与异步抢占部分,说明 GMP 试图解决的全局竞争和长期占用问题;当前细节与 Go 1.27.1 源码交叉核对。
- 国内:饶全成等:GPM 是什么:采用 G 的执行现场、M 的线程身份、P 的局部资源及就绪任务来源,解释三者怎样合作。该文代码基于 Go 1.9.2,不直接沿用其字段和调度细节。
- 国内:欧长坤:调度问题与 GMP 模型:采用多对多模型、可增长栈和从 GM 到 GMP 的动机,说明 P 为什么能够同时改善局部性与执行资源交接。
- 国外:Go runtime 开发文档:Scheduler structures:核对 G、M、P 的官方含义、P 与 GOMAXPROCS 的关系,以及执行普通 Go 代码与执行系统调用的区别。
- 国外:Go 官方博客:Container-aware GOMAXPROCS:依据并行度、容器 CPU 配额及 Go 1.25 起默认值调整的说明,解释正文中 GOMAXPROCS 的默认值为何不总等于机器核数。
相关面经
15 篇字节跳动
AI Agent全栈开发Go 的并发模型有哪些?
水滴
AI全栈拼多多
服务端开发工程师go并发原理,或者说底层调度原理
极智嘉
机器人仿真岗位介绍一下 Go 的 GMP 调度模型。G、M、P 分别是什么?
字节跳动
Agent 开发● 了解 Go 语言和其中的协程吗?
展开剩余 10 篇面经
智元机器人、字节跳动
go语言gc、gmp模型的八股
字节跳动
AI 应用工程师深信服
Agent 开发Go语言GMP调度模型讲一下?
快手
Agent 开发他问了Go channel是不是并发安全,channel和mutex的区别,GMP调度,本地队列为空时怎么偷任务。然后又问Redis为什么快、支持哪些数据结构、怎么做类似淘宝搜索框的实时模糊搜索。最后还问了扫码登录完整流程、服务注册发现、Nacos/Consul心跳。
唯品会
Agent 开发GMP调度模型
拼多多
基础平台开发工程师go的gmp和协程
千寻智能
平台开发工程师GMP
360
全栈开发工程师Go 协程调度
滴滴
AI Agent 后端开发当 `P` 的本地队列为空或不为空时,Go 调度器是怎么调度 `G` 的?
字节跳动
后端开发(Agent 方向)当P的本地队列为空或者不为空时,它会怎么去调度G(协程)?








