当前位置: 首页 > news >正文

第五章:Go运行时、内存管理与性能优化之Go调度器 (GMP模型) 详解

Go 调度器 (GMP 模型) 详解

在高并发编程领域,Go 以 Goroutine 的轻量级并发能力著称,而这一切的背后核心机制就是 GMP 调度模型。理解 GMP 模型,不仅能帮助我们写出更高性能的 Go 代码,还能在调优和排查问题时做到“知其然,也知其所以然”。

本文将深入拆解:

  • G(Goroutine)、M(OS Thread)、P(Processor)三者的角色
  • Goroutine 的生命周期
  • 协作式调度与抢占式调度(Go 1.14+)
  • 工作窃取 (Work Stealing) 机制
  • 性能调优的扩展思考

1. Go 调度器总体架构

Go 程序启动时,运行时会创建一个调度器(Scheduler),负责管理所有 Goroutine 的运行。调度器将 Goroutine 绑定到真实的 CPU 核心,并映射到系统线程,从而实现高效调度。

G、M、P 各是什么?

名称全称含义
GGoroutine用户态的“协程”,Go 运行时调度的最小执行单元,包含栈、寄存器、任务函数等上下文信息。
MMachineOS 级线程(thread),代码最终在 M 上执行。
PProcessor调度器的一个逻辑处理器,保存一个 G 的队列和调度上下文,管理本地运行队列。

可以理解为:

  • G 是具体的要执行的任务
  • M 是硬件实际计算资源(线程)
  • P 是 M 和 G 之间的桥梁,它承担本地 G 队列、调度上下文、内存分配缓存等职责

2. 三者的关系

多个 G → 分布在 P 的队列中
每个 M 必须绑定一个 P 才能执行 G
P 的数量 == runtime.GOMAXPROCS(默认等于 CPU 核心数)

+-------+     +-------+     +-------+
|   G   |     |   G   |     |   G   |
+---+---+     +---+---+     +---+---+|             |             |v             v             v
+------------------------------------+
|                P1                  |
| [G队列] G1 G2 G3                    |
+------------------------------------+|v
+------------------------------------+
|                M1                  |
| OS Thread 执行绑定的 P 中的 G       |
+------------------------------------+

在这里插入图片描述

3. Goroutine 生命周期

一个 Goroutine 从创建到结束,大致经历以下阶段:

  1. 创建(newproc)

    go myFunc()
    

    编译器会调用运行时的 runtime.newproc 创建 G 结构体,并将其放入当前 P 的本地队列。

  2. 调度
    调度器从 P 的本地队列取 G,分配给当前绑定的 M 执行。

  3. 运行(Running)
    M 取到 G 后执行其栈帧上的函数。

  4. 阻塞 / 挂起(Waiting)
    如果系统调用、channel 操作、锁阻塞等,就会让 G 挂起,从 M 上解绑,并将 M 释放去执行其他可运行的 G。

  5. 结束(Dead)
    执行完毕,进入空闲列表,供下次复用。


4. 协作式调度与抢占式调度

协作式调度(pre-1.14)

Go 早期调度器是协作式的:G 只有在可能阻塞的地方(函数调用、channel、select、for 循环中的函数调用)才会让出控制权给调度器。这种方式实现简单,但如果一个 G 长时间执行 CPU 密集任务(比如死循环),调度器无法介入,可能会饿死其他 G。

func main() {go func() {for { } // 死循环,协作式下会阻塞调度}()time.Sleep(time.Second)fmt.Println("Done")
}

上面在 Go <1.14 版本可能导致 Done 无法输出。


抢占式调度(Go 1.14+)

Go 1.14 引入了基于信号(async preemption)的抢占式调度

  • 编译器会在函数调用时插入安全点(safe point)
  • 运行时会周期性发送抢占信号给运行 G 的 M
  • 收到信号后,M 会在下一个 safe point 停止当前 G,把执行权交回调度器

这样,即使是死循环,也能被调度器强制切换,避免 CPU 被某个 G 长时间垄断。


5. 工作窃取(Work Stealing)机制

每个 P 都有一个本地 G 队列(Local Run Queue)和一个全局 G 队列(Global Run Queue)。

  • 调度器优先从本地队列取 G,减少锁竞争
  • 当本地队列耗尽,会从全局队列或其他 P “偷” 一半任务

示意图:

P1: [G1, G2]  --> 执行完后去 P2 队列偷一半: [G5]
P2: [G3, G4, G5, G6]  --> 被偷走 G5

好处:

  • 动态负载均衡
  • 避免某个 P 队列耗尽而其他 P 积压任务

6. 示例:观察 GMP 调度

我们可以用 runtime 包提供的调试参数观察调度行为:

package mainimport ("fmt""runtime""time"
)func main() {runtime.GOMAXPROCS(2) // 限制 P 数量for i := 0; i < 4; i++ {go func(id int) {for {fmt.Printf("Goroutine %d running on thread %d\n", id, runtime.Getg().M.ID)time.Sleep(time.Millisecond * 100)}}(i)}time.Sleep(time.Second * 2)
}

注意:runtime.Getg() 是运行时内部函数,不能直接调用,真实环境可用 pprof/trace 工具分析。


7. 调优与扩展

调优方向

  1. 合理设置 GOMAXPROCS

    • 对 CPU 密集型任务:设置为 CPU 核心数
    • 对 IO 密集型任务:可适当放大
    runtime.GOMAXPROCS(runtime.NumCPU())
    
  2. 避免创建过多 Goroutine

    • 虽然轻量,但调度开销仍存在(栈切换、抢占)
    • 建议使用池化(如 sync.Pool、worker pool)
  3. 减少高基数锁竞争

    • 尽量让任务在同一个 P 执行
    • 利用 channel 缓冲减少切换频率
  4. 利用 pprof 分析 CPU 占用和调度瓶颈

    go tool pprof http://localhost:6060/debug/pprof/profile
    

8. 总结

GMP 模型是 Go 高并发能力的基石:

  • G:任务单元
  • M:系统线程
  • P:调度资源绑定器
http://www.dtcms.com/a/353558.html

相关文章:

  • 【工具】基于LabelImg标注数据安装运行全流程
  • 运算符(3)
  • Typora Markdown编辑器 (Mac中文)
  • PlantUML描述《分析模式》第3章观察和测量(1)
  • 基于PCIE的全国产化多通道AD数据采集卡
  • GIT压缩提交,将多个已经push的commit提交,合并成一个
  • C#实战:基于iTextSharp实现PDF加密小工具
  • spring-ai-alibaba使用
  • 工业机器人如何通过ModbusTCP转Profinet实现与西门子PLC通讯?
  • Node.js(4)—— http模块基础
  • 终极指南:批量自动化处理.gz压缩文件内的中文编码乱码问题
  • 使用人工智能写一个websocket聊天页面
  • 《websocketpp使用指北》
  • 媒体发布平台哪家好?软文营销专业服务商测评推荐
  • 教程:计算中国县级耕地 NDVI 均值并导出 CSV(MODIS)
  • MySQL 基础:DDL、DML、DQL、DCL 四大类 SQL 语句全解析
  • Windows系统Docker中Xinference 集群无法启动的解决方法
  • 深度剖析HTTP和HTTPS
  • LIO-SAM的后端
  • 【stm32简单外设篇】-4×4 薄膜键盘
  • 主流技术栈 NestJS、TypeScript、Node.js版本使用统计
  • 打印机共享修复,打印机无法共享,打印机修复工具下载及安装
  • ChatGPT 上线 “学习模式”:全版本开放,重构 AI 教育逻辑
  • 《电商库存系统超卖事故的技术复盘与数据防护体系重构》
  • 设计模式:桥接模式(Bridge Pattern)
  • C# 使用抽象工厂模式实现花园规划系统的设计与实现
  • electron离线开发核心环境变量npm_config_cache
  • python自学笔记14 NumPy 线性代数
  • 嵌入式linux相机(1)
  • Chrome插件开发【storage】