跳转到主内容
趣航编程网 - 趣学编程,启航技术之路!

Go 语言调度器 GMP 模型,你真的懂了吗?

各位编程爱好者,你是否曾经对 Go 语言的调度器 GMP 模型感到困惑?今天,我就来给大家深度解析一下这个模型,让你轻松理解 Go 的并发之道。

一、为什么需要 GMP?

在现代高并发场景下,传统的内核级线程(Thread)存在以下痛点:

  • 内存消耗高:一个线程初始栈通常为 2MB,而 Goroutine 仅需 2KB。
  • 切换成本高:线程切换涉及内核态/用户态转换,上下文切换耗时约 1-2 微秒。
  • 调度开销大:内核调度器是通用的,无法针对语言特性(如 GC、Channel)优化。

Go 引入了 Goroutine,在 用户态实现轻量级线程,由 Go 运行时(Runtime)自行调度。

二、调度器的前世今生(GM → GMP)

旧调度器(Go 1.1 之前):只有 G 和 M。所有的 G 存储在一个全局队列中,M 每次获取 G 都要加全局锁。这导致了严重的锁竞争,且由于缺乏 P,无法保证局部性(Cache Locality)。

新调度器(GMP,Go 1.1 引入):引入了 P (Processor) 作为中间层,解决了锁竞争,并实现了更高效的本地队列。

三、核心组件:G、M、P 的深度拆解

G (Goroutine):协程。保存了执行栈、状态、以及要执行的任务函数。

M (Machine):内核线程。它是真正的执行单元,由操作系统调度。它不保存 G 的状态,只是负责运行代码。

P (Processor):逻辑处理器。调度上下文,解耦了 M 和 G。

核心作用: 持有局部运行队列(Local Queue),解耦了 G 和 M。

数量: 由 $GOMAXPROCS$ 决定,通常等于 CPU 核心数。

一句话总结:G 负责干活,M 负责执行,P 负责调度。

3.1 G:Goroutine 的本质

在 GMP 模型中,G (Goroutine) 是执行单元的抽象。很多人说“Goroutine 是轻量级线程”,但这只是表象。从源码层面看,G 实际上是一个由 Go Runtime 管理的用户态结构体。

为什么 Goroutine 初始栈只有 2KB?这是 Go 能够支持百万级并发的关键。我们可以从对比和源码常数两个维度来看:

  • 内存的精打细算:对比进程/线程:Linux 线程栈通常默认为 2MB - 8MB。如果你开 10 万个线程,仅栈内存就需要 200GB,物理内存直接爆表。
  • Go 的策略:Go 团队发现,绝大多数协程在生命周期内并不需要巨大的栈空间。为了实现“高并发”,必须极度压缩初始内存。在 Go 1.4 之后,初始栈被定为了 2048 Byte (2KB)。

源码中的定义:在 src/runtime/stack.go 中,你可以找到这个神奇数字的定义:...

栈的“伸缩艺术”:从分段栈到连续栈...

拆解 runtime.g 结构体:G 到底记住了什么?...

栈管理:stack 与 stackguard...

现场保存:sched (gobuf)...

g.param 的妙用...

状态监控:atomicstatus...

gcAssistBytes 的机制...

sudog —— 协程的“代理人”...

g 结构体中的 M 引用...

g 的“复用”机制...

...

3.2 M:真正跑在 CPU 上的执行者

M 是操作系统线程(OS Thread)的抽象,它是真正的执行单元,负责执行 G 里的代码指令,并且必须绑定一个 P 才能执行 Go 代码。

为什么 M 不直接调度 G?...

M 的核心组成(源码 runtime.m 简析)...

g0 是灵魂...

curg 与 p...

自旋状态 (spinning)...

...

3.3 P (Processor):调度器真正的核心

P 是 GMP 模型的核心大脑。它不是真实的物理 CPU,而是执行 Go 代码所需的资源上下文。

为什么需要 P?...

P 的核心组成(源码 runtime.p 简析)...

runnext 是响应速度的秘密...

runq[256] 限制...

runnext与 runq 的配合...

mcache 本地化分配...

timers 定时器外包...

...

终章:一次 Goroutine 的完整生命周期

...

结语:为什么我们要懂 GMP?

穿透 fmt.Println 的表象,你会发现这其实是一场由 M0 发起、G0 坐镇、P 资源统筹的宏大叙事。在 Go 的世界观里:

  • G 是我们的业务逻辑,代表“谁要干活”。
  • M 是物理世界的算力,代表“由谁来干”。
  • P 是连接两者的纽带,代表“资源如何分配”。

理解 GMP,不仅是为了应付面试,更是为了理解 Go 如何在底层通过局部性原理避开锁竞争,如何通过异步抢占终结死循环。正是这种“让逻辑层与物理层解耦”的设计方案,才让 Go 在高并发的巨浪中,依然能保持极致的优雅与稳定。

—— 顺亿,趣航编程网

相关文章