Go 调度器 (GMP) 揭秘:从汇编角度看 Goroutine 是如何实现“协程切换”的?

标签:#Go #Golang #GMP #Assembly #Runtime #Concurrency


🚀 前言:GMP 的本质是“复用”

操作系统线程(OS Thread)太重了。创建一个线程需要 1-8MB 栈内存,切换一次需要进入内核态,耗时 1-2 微秒。
Go 的 GMP 模型本质上是一个二级调度系统

  • G (Goroutine): 仅仅是一个数据结构 (struct g),包含自己的栈和指令指针 (PC),初始只占 2KB。
  • M (Machine): 真正的 OS 线程。它不懂什么协程,它只知道执行代码。
  • P (Processor): 逻辑处理器,维护了一个本地运行队列(Local Run Queue)。

核心秘密:M 并不直接执行 G 的代码,而是通过一个名为g0的特殊 Goroutine 来充当“调度中介”。
所有的切换,都不是 G 到 G 直连,而是 G1 -> g0 -> G2。


🧬 一、 切换的“黑盒”:gobuf

在 Go 的源码runtime/runtime2.go中,struct g里有一个至关重要的字段:sched
它的类型是gobuf。这就是保存 Goroutine “灵魂”的地方。

typegobufstruct{spuintptr// Stack Pointer (栈顶指针)pcuintptr// Program Counter (指令指针/下一条要执行的指令)g guintptr// 属于哪个 Gctxt unsafe.Pointer retuintptrlruintptrbpuintptr// Base Pointer (栈底指针)}

协程切换的本质,就是把 CPU 的寄存器(SP, PC, BP 等)保存到当前 G 的gobuf里,然后从目标 G 的gobuf里把寄存器恢复出来。


🎬 二、 切出 (Yield):mcallg0

当一个 Goroutine 因为channel阻塞、系统调用或被抢占时,它会调用runtime.mcall
mcall的作用是:保留案发现场,切换到g0栈,开始调度。

让我们看runtime/asm_amd64.s中的汇编代码(Plan9 汇编):

// func mcall(fn func(*g)) TEXT runtime·mcall(SB), NOSPLIT, $0-8 // 1. 取出参数 fn (通常是 schedule 函数) MOVQ fn+0(FP), DI // 2. 获取当前运行的 g (我们称之为 g_cur) get_tls(CX) MOVQ g(CX), AX // AX = g_cur // 3. 保存现场!将寄存器值写入 g_cur.sched (gobuf) MOVQ 0(SP), BX // 保存调用者的 PC (返回地址) MOVQ BX, (g_sched+gobuf_pc)(AX) LEAQ 8(SP), BX // 保存调用者的 SP MOVQ BX, (g_sched+gobuf_sp)(AX) MOVQ BP, (g_sched+gobuf_bp)(AX) // 保存 BP // 4. 切换堆栈!从 g_cur 切换到 g0 MOVQ g_m(AX), BX // BX = g_cur.m (获取当前 M) MOVQ m_g0(BX), SI // SI = m.g0 (获取 g0) // 关键一跳:把 CPU 的 SP 寄存器修改为 g0 的栈顶 MOVQ (g_sched+gobuf_sp)(SI), SP // 5. 现在我们已经运行在 g0 的栈上了 // 设置当前 g 为 g0 MOVQ SI, g(CX) // 6. 执行调度函数 fn(g_cur) // 这里的 AX 还是旧的 g_cur,作为参数传给 schedule PUSHQ AX MOVQ DI, DX CALL DX

人话翻译:
正在跑的 G 说:“我不行了,我要休息。”
于是它把自己的 SP、PC 记在小本本(gobuf)上,然后把 CPU 的 SP 指针瞬间指向了g0的栈。
瞬间,CPU 就以为自己一直是在g0上运行。接着,g0开始执行schedule()函数,去队列里找下一个幸运儿。


🚀 三、 切入 (Resume):gogo

schedule()找到下一个要运行的 G(我们叫它g_next)后,会调用runtime.execute,最终调用汇编实现的runtime.gogo
gogo的作用是:读取g_next的存档,通过修改寄存器,让 CPU “穿越”到g_next上次暂停的地方。

// func gogo(buf *gobuf) TEXT runtime·gogo(SB), NOSPLIT, $16-8 // 1. buf 是 g_next.sched MOVQ buf+0(FP), BX // BX = gobuf // 2. 从 gobuf 恢复寄存器 MOVQ gobuf_g(BX), DX MOVQ 0(DX), CX // 检查 g 是否为 nil get_tls(CX) MOVQ DX, g(CX) // 将 TLS (Thread Local Storage) 设置为 g_next MOVQ gobuf_sp(BX), SP // 🔥 恢复栈指针 SP!此刻切换完成 MOVQ gobuf_ret(BX), AX MOVQ gobuf_ctxt(BX), DX MOVQ gobuf_bp(BX), BP // 恢复 BP // 3. 准备起跳 // 清空 gobuf.sp,防止重复使用 MOVQ $0, gobuf_sp(BX) // 4. 获取下一条指令地址 PC MOVQ gobuf_pc(BX), BX // 5. 飞!跳转到 g_next 的代码位置 JMP BX

人话翻译:
g0说:“也就是你了,g_next。”
于是g0g_next的小本本拿出来,把 CPU 的 SP、BP 全部改成g_next的值。
最后由JMP BX指令,直接跳转到g_next上次停下的代码行。
对 CPU 来说,仿佛什么都没发生过,只是继续执行下一行指令而已。


🔄 四、 宏观流程:G1 -> G2

将上述两个过程结合,就是一次完整的协程切换。

切换流程图 (Mermaid):

G2 用户栈

g0 系统栈

G1 用户栈

JMP

G1 运行中

调用 mcall

mcall: 保存 G1 现场到 gobuf

mcall: 切换 SP 到 g0

执行 runtime.schedule

找到 G2

执行 runtime.execute

调用 gogo

gogo: 从 gobuf 恢复 G2 现场

G2 恢复运行


📊 五、 为什么 Go 切换这么快?

对比一下 Linux 线程切换和 Goroutine 切换:

维度Linux 线程切换Goroutine 切换
模式用户态 -> 内核态 -> 用户态纯用户态
内存涉及页表切换、L1/L2 Cache 失效仅涉及少量寄存器、L1 Cache 亲和性好
寄存器保存所有通用寄存器 + AVX/FPU 状态只保存 SP, PC, BP 等少数几个
耗时~1-2 微秒 (us)~0.2 微秒 (us)

结论:Go 通过在用户空间复写了一套微型操作系统(Runtime),避免了昂贵的系统调用(System Call)开销。


🎯 总结

  • GMP 的 M是执行载体,G是数据存档。
  • g0是连接 G1 和 G2 的桥梁,所有调度逻辑都在 g0 栈上跑。
  • mcall负责“存档并切到 g0”。
  • gogo负责“读档并切回用户 G”。
  • Go 的汇编魔法JMPMOV SP实现了这一切。

Next Step:
既然看懂了切换,建议下载Delve调试器,在汇编层面单步调试一次 Goroutine 的切换过程。在断点处输入disass,亲眼看看MOVQ (g_sched+gobuf_sp)(AX), SP这行指令是如何改变世界线的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1179208.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【创新未发表】基于matlab鸡群算法CSO和自适应双种群协同鸡群算法ADPCCSO无人机避障三维航迹规划【含Matlab源码 14980期】

💥💥💥💥💥💥💞💞💞💞💞💞💞💞欢迎来到海神之光博客之家💞💞💞&#x1f49…

第 174 场双周赛Q2——3810. 变成目标数组的最少操作次数

题目链接:3810. 变成目标数组的最少操作次数(中等) 算法原理: 解法:模拟 38ms击败11.30% 时间复杂度O(N) ①先计算出哪些是需要修改的 ②统计需要修改的下标 ③原数组中相同的数可以一起修改,所以只要统计不…

【无人机三维路径规划】基于matlab鸡群算法CSO和自适应双种群协同鸡群算法ADPCCSO复杂山地模型下无人机路径规划【含Matlab源码 14981期】

💥💥💥💥💥💥💞💞💞💞💞💞💞💞欢迎来到海神之光博客之家💞💞💞&#x1f49…

Day22-20260118

摘要 本文实现了一个基于Java的交互式计算器程序,具有加减乘除四种运算功能。程序通过定义四个独立的方法(add、subtract、multiply、divide)实现基本运算,采用while循环和switch结构实现用户交互界面。用户可以循环…

强烈安利9个AI论文写作软件,专科生搞定毕业论文!

强烈安利9个AI论文写作软件,专科生搞定毕业论文! 论文写作的救星,AI 工具如何改变你的学术之路 对于专科生来说,毕业论文可能是大学生活中最令人头疼的一关。从选题、查资料到撰写、修改,每一步都充满了挑战。而如今&a…

【雷达跟踪】基于matlab面向目标跟踪的雷达干扰方法:提升航空器战场生存力的关键技术【含Matlab源码 14983期】复现含文献

💥💥💥💥💥💥💞💞💞💞💞💞💞💞欢迎来到海神之光博客之家💞💞💞&#x1f49…

【雷达相控阵】毫米波相控阵中空间Zadoff-Chu调制快速波束对准【含Matlab源码 14977期】

💥💥💥💥💥💥💥💥💞💞💞💞💞💞💞💞💞Matlab领域博客之家💞&…

卷积神经网络的开端:$LeNet-5$

卷积神经网络的开端:\(LeNet-5\)[!NOTE] LeNet-5神经网络是1998年YANN LECUN等人在论文Gradient-Based Learning Applied to Document Recognition中提出的一种颠覆性的算法。 说实在的该方法本来是用来解决手写字母也…

【雷达相控阵】基于matlab毫米波相控阵中空间Zadoff-Chu调制快速波束对准【含Matlab源码 14977期】

💥💥💥💥💥💥💞💞💞💞💞💞💞💞欢迎来到海神之光博客之家💞💞💞&#x1f49…

【心电信号ECG】SVM心电图心搏检测与分类【含Matlab源码 14982期】复现含文献

💥💥💥💥💥💥💥💥💞💞💞💞💞💞💞💞💞Matlab领域博客之家💞&…

【心电信号ECG】基于matlab SVM心电图心搏检测与分类【含Matlab源码 14982期】复现含文献

💥💥💥💥💥💥💞💞💞💞💞💞💞💞欢迎来到海神之光博客之家💞💞💞&#x1f49…

【心血管疾病】心脏病数据集Kaggle医学特征二元分类预测心血管疾病【含Matlab源码 14984期】含报告

💥💥💥💥💥💥💥💥💞💞💞💞💞💞💞💞💞Matlab领域博客之家💞&…

社交网络数据科学:完整项目实战指南

社交网络数据科学:完整项目实战指南 引言 痛点引入:你可能遇到的「社交网络分析困境」 作为数据科学爱好者,你是否曾有过这样的困惑: 想学社交网络分析,但看着「图论」「中心性」「社区发现」等术语望而却步&#…

Hive与DynamoDB集成:云原生大数据方案

Hive与DynamoDB集成:云原生大数据方案 关键词:Hive、DynamoDB、云原生、大数据集成、数据处理 摘要:本文聚焦于Hive与DynamoDB的集成,旨在探讨云原生环境下的大数据解决方案。首先介绍了Hive和DynamoDB的背景信息,包括它们的特点和适用场景。接着详细阐述了两者集成的核心…

【创新未发表】鸡群算法CSO和自适应双种群协同鸡群算法ADPCCSO无人机避障三维航迹规划【含Matlab源码 14980期】

💥💥💥💥💥💥💥💥💞💞💞💞💞💞💞💞💞Matlab领域博客之家💞&…

Flutter × OpenHarmony 跨端汇率转换应用:货币数据模型与页面实现

文章目录Flutter OpenHarmony 跨端汇率转换应用:货币数据模型与页面实现前言背景Flutter OpenHarmony 跨端开发介绍开发核心代码代码解析心得总结Flutter OpenHarmony 跨端汇率转换应用:货币数据模型与页面实现 前言 在全球化经济背景下,…

【无人机三维路径规划】鸡群算法CSO和自适应双种群协同鸡群算法ADPCCSO复杂山地模型下无人机路径规划【含Matlab源码 14981期】

💥💥💥💥💥💥💥💥💞💞💞💞💞💞💞💞💞Matlab领域博客之家💞&…

【雷达跟踪】面向目标跟踪的雷达干扰方法:提升航空器战场生存力的关键技术【含Matlab源码 14983期】复现含文献

💥💥💥💥💥💥💥💥💞💞💞💞💞💞💞💞💞Matlab领域博客之家💞&…

数电实验7【计数器和跑马灯设计实验报告】数字电路 逻辑与计算机设计 logisim

上一篇:数电实验6【寄存器及手动加法设计实验报告】数字电路 逻辑与计算机设计 logisim 目录 实验资料 实验报告 一、实验目的 二、实验环境 三、实验内容 四、实验步骤 五、实验心得 实验资料 点击下载 实验报告 一、实验目的 理解时钟的作用 能够设…

人工智能之核心基础 机器学习 第十七章 Scikit-learn工具全解析

人工智能之核心基础 机器学习 第十七章 Scikit-learn工具全解析 文章目录人工智能之核心基础 机器学习17.1 Scikit-learn 简介与安装📌 是什么?✅ 核心优势🔧 安装(推荐使用虚拟环境)17.2 Scikit-learn 核心 API 使用…