【Go并发编程】Goroutine 调度器揭秘:从 GMP 模型到 Work Stealing 算法

news/2025/2/21 16:19:06/

每天一篇Go语言干货,从核心到百万并发实战,快来关注魔法小匠,一起探索Go语言的无限可能!

在 Go 语言中,Goroutine 是一种轻量级的并发执行单元,它使得并发编程变得简单高效。而 Goroutine 的高效调度机制是 Go 语言在并发处理上的一大亮点。本文将深入剖析 Go 语言的 Goroutine 调度器,从 GMP 模型到 Work Stealing 算法,带你一探究竟。

一、Goroutine 调度器的背景

Go 语言的并发模型基于 Goroutine,它是一种轻量级的线程,由 Go 运行时(runtime)自动管理。Goroutine 的调度机制决定了多个 Goroutine 如何高效地映射到操作系统线程上执行。与传统线程(Thread)相比具有以下优势:

  1. 内存占用仅2KB(线程默认1MB)
  2. 上下文切换成本仅0.2μs(线程约1μs)
  3. 创建速度达到微秒级(线程需毫秒级)

但是Goroutine本质上是用户态线程,需要依赖GMP调度器将其映射到操作系统线程(M)执行。

二、GMP 模型:Goroutine 调度的核心

Goroutine 的调度基于 GMP 模型,即 Goroutine(G)、Machine(M)和 P(Processor)的组合。这个模型实现了从 N:1(用户态线程到内核态线程)到 N:M(用户态线程到内核态线程的灵活映射)的调度。

1. Goroutine(G)

Goroutine 是用户定义的协程,它代表了并发执行的任务。创建 Goroutine 的底层方法是newproc 函数,它会将 Goroutine 放入 P 的本地队列中。如果本地队列已满,则放入全局队列中。

go func() {// 任务代码
}()
2. Machine(M)

Machine 代表操作系统线程,是 Go 运行时与操作系统交互的接口。Go 运行时会根据需要创建和销毁 M,以适应不同的并发场景。

3. Processor(P)

Processor 是 Go 运行时中的调度上下文,它负责管理 Goroutine 的调度。每个 P 有自己的本地队列,用于存储待执行的 Goroutine。

4.GMP模型示意图

通过该示意图可以了解到完整的GMP模型关系。

全局队列:本地队列(Processor调度器管理)满了的情况下,将会把新创建的Goroutine加入到全局队列中排队等待执行。

本地队列:存放即将执行的Goroutine,每个processor中的goroutine将并行执行。

Goroutine(G):图中的每个圆形图标G就是代表一个Groutine。

Processor(P):管理当前调度器内的本地队列,并负责管理 Goroutine 的调度,用于存储待执行的 Goroutine。processor和groutine是N:M的关系。

内核线程(M):每个M代表了一个内核线程,操作系统调度器负责把内核线程分配到CPU的核上执行。

三、调度器的工作流程

Go 调度器的核心任务是从队列中获取可执行的 Goroutine,并将其分配给可用的 M 执行。

1. 本地队列

每个 P 都有一个本地队列,调度器会优先从本地队列中获取 Goroutine 执行。如果本地队列为空,则会尝试从全局队列获取。

2. 全局队列

全局队列是所有 P 共享的队列,用于存储未被分配的 Goroutine。当本地队列为空时,调度器会尝试从全局队列中获取 Goroutine。

3. Work Stealing(工作窃取)

如果本地队列和全局队列都为空,调度器会采用 Work Stealing 算法,从其他 P 的本地队列中“偷取” Goroutine。这种策略可以实现线程之间的负载均衡。

func runqsteal(pp, p2 *p, stealRunNextG bool) *g {t := pp.runqtailn := runqgrab(p2, &pp.runq, t, stealRunNextG)if n == 0 {return nil}n--gp := pp.runq[(t+n)%uint32(len(pp.runq))].ptr()if n == 0 {return gp}h := atomic.LoadAcq(&pp.runqhead)if t-h+n >= uint32(len(pp.runq)) {throw("runqsteal: runq overflow")}atomic.StoreRel(&pp.runqtail, t+n)return gp
}

四、抢占式调度

Go 调度器采用抢占式调度策略,以防止某个 Goroutine 占用过多 CPU 资源。在 Go 1.14 之后,调度器在任何安全点都可以进行抢占。

func findRunnable() (gp *g, inheritTime, tryWakeP bool) {mp := getg().mtoppp := mp.p.ptr()// 每61次调度周期就检查一次全局G队列if pp.schedtick%61 == 0 && sched.runqsize > 0 {lock(&sched.lock)gp := globrunqget(pp, 1)unlock(&sched.lock)if gp != nil {return gp, false, false}}// 本地队列if gp, inheritTime := runqget(pp); gp != nil {return gp, inheritTime, false}// 全局队列if sched.runqsize != 0 {lock(&sched.lock)gp := globrunqget(pp, 0)unlock(&sched.lock)if gp != nil {return gp, false, false}}// 工作窃取if mp.spinning || 2*sched.nmspinning.Load() < gomaxprocs-sched.npidle.Load() {if !mp.spinning {mp.becomeSpinning()}gp, inheritTime, _, _, _ := stealWork(now)if gp != nil {return gp, inheritTime, false}}return nil, false, false
}

五、协作式调度

除了抢占式调度,Go 还支持协作式调度。Goroutine 可以通过调用runtime.Gosched() 函数主动让出 CPU 的执行权。

func main() {go func() {for i := 0; i < 10; i++ {fmt.Println("Goroutine 1")runtime.Gosched()}}()for i := 0; i < 10; i++ {fmt.Println("Goroutine 2")}
}

六、总结

Go 语言的 Goroutine 调度机制通过 GMP 模型和 Work Stealing 算法实现了高效的并发执行。抢占式调度和协作式调度策略确保了 Goroutine 的公平执行,而 Work Stealing 算法则进一步提高了多核处理器上的负载均衡。通过这些机制,Go 运行时能够高效地利用系统资源,实现高性能的并发编程。


http://www.ppmy.cn/news/1573507.html

相关文章

在IDEA中误操作Git Rollback后的恢复方法

一、问题描述 在使用IDEA进行代码管理时&#xff0c;通过Git提交代码过程中不小心点击了“Rollback”选项&#xff0c;导致尚未提交的代码丢失。 二、解决办法 找到目标模块&#xff1a;在项目视图中选中需要恢复的文件或目录。 查看本地历史&#xff1a;右键点击&#xff0…

计算机视觉:卷积神经网络(CNN)基本概念(一)

第一章&#xff1a;计算机视觉中图像的基础认知 第二章&#xff1a;计算机视觉&#xff1a;卷积神经网络(CNN)基本概念(一) 第三章&#xff1a;计算机视觉&#xff1a;卷积神经网络(CNN)基本概念(二) 第四章&#xff1a;搭建一个经典的LeNet5神经网络(附代码) 第五章&#xff1…

深度学习中的知识蒸馏

知识蒸馏&#xff08;Knowledge Distillation&#xff09;是一种模型压缩技术&#xff0c;旨在将大型、复杂的模型&#xff08;通常称为教师模型&#xff09;的知识迁移到小型、简单的模型&#xff08;学生模型&#xff09;中。通过这种方式&#xff0c;学生模型可以在保持较高…

使用Java爬虫获取京东商品SKU信息的完整指南

在电商领域&#xff0c;商品SKU&#xff08;Stock Keeping Unit&#xff09;信息是商家和消费者都非常关注的内容。SKU信息不仅包括商品的基本属性&#xff08;如价格、库存、规格等&#xff09;&#xff0c;还涉及到商品的动态数据&#xff08;如促销信息、库存状态等&#xf…

深入解析 Flutter GetX

深入解析 Flutter GetX&#xff1a;从原理到实战 GetX 是 Flutter 中一个轻量级且功能强大的状态管理、路由管理和依赖注入框架。它以简单、快速、高效著称&#xff0c;适合从小型到大型项目的开发需求。GetX 的设计理念是一体化解决方案&#xff0c;通过一个框架解决状态管理…

【ArcGIS Pro二次开发】(87):样式_Style的用法

.Stylx类型的文件即为样式库文件&#xff0c;保存了符号样式。 1、根据名字获取当前工程中的style //获取当前工程中的所有style var ProjectStyles Project.Current.GetItems<StyleProjectItem>();//根据名字找出指定的style StyleProjectItem style ProjectStyles.F…

Linux中安装open-webui报sqlite版本低的解决办法

almalinux中安装好open-webui&#xff0c;启动服务时报如下错&#xff1a; RuntimeError: [91mYour system has an unsupported version of sqlite3. Chroma requires sqlite3 > 3.35.0.[0m [94mPlease visit https://docs.trychr…

Nat. Genet | 单细胞多组回归模型识别功能和疾病相关增强子,并实现染色质潜力分析

Nat. Genet | 单细胞多组回归模型识别功能和疾病相关增强子&#xff0c;并实现染色质潜力分析 本文提出了一种名为SCARlink的基因调控模型&#xff0c;通过结合单细胞RNA测序&#xff08;scRNA-seq&#xff09;和单细胞开放染色质测序&#xff08;scATAC-seq&#xff09;数据&…