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 共享进程内存,访问共享状态也仍需要同步。

参考资料

相关面经

15 篇

字节跳动

AI Agent全栈开发
查看原面经 →
Go 的并发模型有哪些?

水滴

AI全栈
校招一面
查看原面经 →
介绍一下 Go 的 GMP 调度模型。
Go 调度器中的 Work Stealing 是怎么实现的?

拼多多

服务端开发工程师
秋招二面
查看原面经 →
go并发原理,或者说底层调度原理

极智嘉

机器人仿真岗位
查看原面经 →
介绍一下 Go 的 GMP 调度模型。G、M、P 分别是什么?

字节跳动

Agent 开发
校招秋招一面
查看原面经 →
● 了解 Go 语言和其中的协程吗?
展开剩余 10 篇面经

智元机器人、字节跳动

面经合集
查看原面经 →
go语言gc、gmp模型的八股

字节跳动

AI 应用工程师
一面
查看原面经 →
阻塞状态的 goroutine 是怎么处理的?和线程有什么不同?
goroutine和线程切换的时候,有什么不同?
假设服务器上有10000个goroutine,此时它们是如何调度的?

深信服

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(协程)?

收录题集

1 个来源,共1 份题集

相关题目

4 题