Golang-GMP模型

news2024/11/27 16:47:18

写在前面

Go 为了自身 goroutine 执行和调度的效率,自身在 runtime 中实现了一套 goroutine 的调度器,下面通过一段简单的代码展示一下 Go 应用程序在运行时的 goroutine,方便大家更好的理解。

The Go scheduler is part of the Go runtime, and the Go runtime is built into your application

for i := 0; i < 4; i++ {
        go func() {
                time.Sleep(time.Second)
        }()
}
fmt.Println(runtime.NumGoroutine())

上面这段代码的输出为:5 说明当前这个应用程序中存在 goroutine 的数量是 5,事实上也符合我们的预期。那么问题来了,这 5 个 goroutine 作为操作系统用户态的基本调度单元是无法直接占用操作系统的资源来执行的,必须经过内核级线程的分发,这是操作系统内部线程调度的基本模型,根据用户级线程和内核级线程的对应关系可以分为 1 对 1,N 对 1 以及 M 对 N 这三种模型,那么上述的 5 个 goroutine 在内核级线程上是怎么被分发的,这就是 Go语言的 goroutine 调度器决定的。

GMP 模型

整个 goroutine 调度器的实现基于 GMP 的三级模型来实现。

  • G:goroutine (go 代码)
  • M:内核级线程,运行在操作系统的核心态。(在 Go 中支持最大的 M 的数量是 10000,但是操作系统中通常情况是不可以创建这么多的线程。)
  • P:processor,可以理解成一个等待分发给 M 调度执行的 goroutine 队列。(P的个数是由 runtime 的 GOMAXPROCS 来决定的。)

M 和 P 存在一一对应的绑定关系。大致的结构图如下所示:

GMP 模型图如下:

  1. 全局队列(Global Queue):存放等待运行的 G。
  2. P 的本地队列:同全局队列类似,存放的也是等待运行的 G,存的数量有限,不超过 256 个。新建 G’时,G’优先加入到 P 的本地队列,如果队列满了,则会把本地队列中一半的 G 移动到全局队列。
  3. P 列表:所有的 P 都在程序启动时创建,并保存在数组中,最多有 GOMAXPROCS(可配置) 个。
  4. M:线程想运行任务就得获取 P,从 P 的本地队列获取 G,P 队列为空时,M 也会尝试从全局队列拿一批 G 放到 P 的本地队列,或从其他 P 的本地队列偷一半放到自己 P 的本地队列。M 运行 G,G 执行之后,M 会从 P 获取下一个 G,不断重复下去。

Goroutine 调度器和 OS 调度器是通过 M 结合起来的,每个 M 都代表了 1 个内核线程,OS 调度器负责把内核线程分配到 CPU 的核上执行。

goroutine 之旅

通常情况下,我们在代码中执行 go func(){}后,GMP 模型是如何工作的?通过一个详细的图来展示一下。

  1. 首先创建一个新的 goroutine
  2. 如果本地的局部队列中有足够的空间可以存放,则放入局部队列中;如果局部队列满,则放入一个全局队列(所有的 M 都可以从全局队列中拉取 G 来执行)
  3. 所有的 G 都必须在 M 上才可以被执行,MP 存在一一绑定的关系,如果 M 绑定的 P 中存在可以被执行的 G,则从 P 中拉取 G 来执行;如果 P 中为空,没有可执行的 G,则 M 从全局队列中拉取;如果全局队列也为空,则从其他的 P 中拉取 G
  4. G 的运行分配必要的资源,等待 CPU 的调度
  5. 分配到 CPU,执行 func(){}

调度策略

整个 goroutine 调度器最重要的调度策略是:复用,避免频繁的资源创建和销毁,最大限度的提升系统的吞吐量和并发程度。这也是操作系统进行线程调度的终极目标。复用(reuse)也是很多「池化技术」的基础。

围绕着这一原则,goroutine 调度器在以下几个方面进行调度策略的优化。

  1. 工作队列的窃取机制:这个跟 Java 中的 ForkJoin Pool 的窃取机制同一原理,都是当线程 M 空闲时,从其他繁忙的队列 P 中"窃取"任务 G 过来执行,而不是销毁空闲的 M。因为线程的创建和销毁是需要消耗系统资源的,避免线程的频繁创建和销毁可以极大的提升系统的并发程度。
  2. 交接机制:当线程M被阻塞的时候,M 会主动将 P 交接给其他空闲的 M

另外,在 go 的 1.14 版本中,go 语言的技术团队尝试在调度器中添加了可抢占的技术. (https://github.com/golang/go/issues/24543)[https://github.com/golang/go/issues/24543]

抢占技术的出现一方面解决了线程 M 在执行计算密集型任务时长时间占用 CPU,导致与之绑定的 P 上的其他 G 得不到执行而造成的"饥饿现象";
另一方面,抢占技术的出现对 GC 来讲解决 GC 时可能出现的 deadLock,相关的 issue 见:关于 GC 时 tight loops 应该可以被抢占的讨论(https://github.com/golang/go/issues/10958)[https://github.com/golang/go/issues/10958]

调度器的生命周期

特殊的 M0 和 G0

  • M0 是启动程序后的编号为 0 的主线程,这个 M 对应的实例会在全局变量 runtime.m0 中,不需要在 heap 上分配,M0 负责执行初始化操作和启动第一个 G, 在之后 M0 就和其他的 M 一样了。
  • G0 是每次启动一个 M 都会第一个创建的 goroutine,G0 仅用于负责调度的 G,G0 不指向任何可执行的函数,每个 M 都会有一个自己的 G0。在调度或系统调用时会使用 G0 的栈空间,全局变量的 G0 是 M0 的 G0。

最开始的 MG 模型

在 go 语言的早期,goroutine 调度器的模型并不是 GMP,而是 GM。整个调度器维护一个全局的 G 的等待队列,所有的 M 从这个全局的队列中拉取 G 来执行,在 go1.1 中将这种模型直接干掉,取而代之的是现在的 GMP 模型,在 GM 模型的基础上增加 P 局部队列。官方之所有这么这么做,原因有三:

  1. 创建、销毁、调度 G 都需要每个 M 获取锁,这就形成了激烈的锁竞争。
  2. M 转移 G 会造成延迟和额外的系统负载。比如当 G 中包含创建新协程的时候,M 创建了 G’,为了继续执行 G,需要把 G’交给 M’执行,也造成了很差的局部性,因为 G’和 G 是相关的,最好放在 M 上执行,而不是其他 M’。
  3. 系统调用 (CPU 在 M 之间的切换) 导致频繁的线程阻塞和取消阻塞操作增加了系统开销。

小结

总结,Go 调度器很轻量也很简单,足以撑起 goroutine 的调度工作,并且让 Go 具有了原生(强大)并发的能力。Go 调度本质是把大量的 goroutine 分配到少量线程上去执行,并利用多核并行,实现更强大的并发。

参考

  • [Golang三关-典藏版] Golang 调度器 GMP 原理与调度全分析
  • Go Scheduler 的 GMP 模型

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

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

相关文章

华为机试题:HJ92 在字符串中找出连续最长的数字串(python)

文章目录&#xff08;1&#xff09;题目描述&#xff08;2&#xff09;Python3实现&#xff08;3&#xff09;知识点详解1、input()&#xff1a;获取控制台&#xff08;任意形式&#xff09;的输入。输出均为字符串类型。1.1、input() 与 list(input()) 的区别、及其相互转换方…

C++语法规则2(C++面向对象)

继承 面向对象程序设计中最重要的一个概念是继承。继承允许我们依据另一个类来定义一个类&#xff0c;这使得创建和维护一个应用程序变得更容易。这样做&#xff0c;也达到了重用代码功能和提高执行效率的效果。 当创建一个类时&#xff0c;您不需要重新编写新的数据成员和成…

SpringBoot修改启动图标(详细步骤)

目录 一、介绍 二、操作步骤 三、介绍Java学习&#xff08;题外话&#xff09; 四、关于基础知识 一、介绍 修改图标就是在资源加载目录&#xff08;resources&#xff09;下放一个banner.txt文件。这样运行加载的时候就会扫描到这个文件&#xff0c;然后启动的时候就会显…

vue -- 自定义指令钩子函数补充 自定义过滤器filter参数

自定义指令补充 自定义指令通过钩子函数的形式来实现自定义的功能 这里是几个常用的钩子函数以及它的方法&#xff1a; bind&#xff1a;只调用一次&#xff0c;指令第一次绑定到元素时调用&#xff0c;在这里可以进行一次性的初始化设置。 inserted&#xff1a;被绑定元素插…

Docker Nginx 反向代理

最近在系统性梳理网关的知识&#xff0c;其中网关的的功能有一个是代理&#xff0c;正好咱们常用的Nginx也具备次功能&#xff0c;今天正好使用Nginx实现一下反向代理&#xff0c;与后面网关的代理做一个对比&#xff0c;因为我使用的docker安装的Nginx&#xff0c;与直接部署N…

web worker的基本使用案例

文件目录如下 代码按照顺序分别如下 webworker.html <!DOCTYPE html> <html lang"en"><head><meta charset"utf-8" /><meta http-equiv"X-UA-Compatible" content"IEedge" /><meta name"viewpo…

C语言函数: 字符串函数及模拟实现strtok()、strstr()、strerror()

C语言函数&#xff1a; 字符串函数及模拟实现strtok()、strstr()、strerror() strstr()函数: 作用&#xff1a;字符串查找。在一串字符串中&#xff0c;查找另一串字符串是否存在。 形参: str2在str1中寻找。返回值是char*的指针 原理&#xff1a;如果在str1中找到了str2&…

为什么这几年参加PMP考试的人越来越多?

PMP认证从国外引进大陆这么多年了&#xff0c;其火热程度依然不减&#xff0c;我个人认为是取决于市场的运作和“游戏规则”&#xff08;岗位招聘和项目招标需要&#xff09;。PMP含金量不算高&#xff0c;更多的是“敲门砖”作用&#xff0c;项目管理岗位的门槛&#xff0c;当…

MySQL日志审计和分析

审核数据库活动是加强数据库安全性的重要组成部分。这涉及识别漏洞&#xff0c;例如默认或弱登录凭据、过多的用户和组权限以及未修补的数据库。攻击者利用这些漏洞来实现自己的目标&#xff0c;如权限提升、SQL 注入和 DoS 攻击。因此&#xff0c;出于安全性和合规性原因&…

源码分析spring如和对@Component注解进行BeanDefinition注册的

Spring ioc主要职责为依赖进行处理&#xff08;依赖注入、依赖查找&#xff09;、容器以及托管的(java bean、资源配置、事件)资源声明周期管理&#xff1b;在ioc容器启动对元信息进行读取&#xff08;比如xml bean注解等&#xff09;、事件管理、国际化等处理&#xff1b;首先…

SPI读写SD卡速度有多快?

SD卡是一个嵌入式中非常常用的外设&#xff0c;可以用于存储一些大容量的数据。但用单片机读写SD卡速度一般都有限&#xff08;对于高速SD卡&#xff0c;主要是受限于单片机本身的接口速度&#xff09;&#xff0c;在高速、实时数据存储时可能会有影响。但具体速度可以达到多少…

Spark RDD的设计与运行原理

一、Spark RDD概念 一个RDD就是一个分布式对象集合&#xff0c;本质上是一个只读的分区记录集合&#xff0c;每个RDD可以分成多个分区&#xff0c;每个分区就是一个数据集片段&#xff0c;并且一个RDD的不同分区可以被保存到集群中不同的节点上&#xff0c;从而可以在集群中的…

Could not resolve dependencies for project

maven 打包Could not resolve dependencies for project和无效的目标发行版: 1.8 1.maven 打包Could not resolve dependencies for project 最近项目上使用的是idea ide的多模块话&#xff0c;需要模块之间的依赖&#xff0c;比如说系统管理模块依赖授权模块进行认证和授权&a…

聊聊关于分类和分割的损失函数:nn.CrossEntropyLoss()

目录 1. nn.CrossEntropyLoss() 2. 多分类中 nn.CrossEntropyLoss() 的应用 3. 分割中 nn.CrossEntropyLoss() 的应用 3.1 测试文件 3.2 输出可视化 3.3 softmax 3.4 log 3.5 CrossEntropyLoss 1. nn.CrossEntropyLoss() 分类中&#xff0c;经常用 nn.CrossEntropyL…

.NET Core Api使用Folder(文件夹)形式发布并指定监听端口

分为以下几个步骤 1. 先安装SDK及运行环境, 无需安装IIS, 因为他不在IIS上运行 环境下载路径, 我用的是.NET 7.0, 可以根据自己的版本下载: 下载 .NET 7.0 SDK (v7.0.201) - Windows x64 Installer 下载.NET运行环境 下载后安装.直接下一步..安装即可 2. 配置发布设置 (…

Nginx服务优化与防盗链

目录 1.隐藏nginx版本号 1.查看版本号 2.隐藏版本信息 2.修改用户与组 3.缓存时间 4.日志分割 5.连接超时 6.更改进程数 7.网页压缩 8.配置防盗链 1.配置web源主机&#xff08;192.168.156.10 www.lhf.com&#xff09; 2.配置域名映射关系 3.配置盗链主机 &#xff0…

python实现波士顿房价预测---(2)

计算梯度 继续上一篇的内容python实现波士顿房价预测—(1)。 梯度计算公式中引入计算因子12\frac{1}{2}21​&#xff0c;为了计算更加简洁。 L12N∑i1N(yi−zi)2L\frac{1}{2N}\sum_{i1}^{N}(y_i - z_i)^2L2N1​∑i1N​(yi​−zi​)2 其中ziz_izi​是模型对于第i个样本的预测值…

【halcon】轮廓拟合相关函数

涉及函数 edges_sub_pix 寻找边缘 edges_sub_pix (Image, Edges, canny, 1, 10, 20) 后面三个参数&#xff0c;越小&#xff0c;找到的细节越多。这个是对应录波器为canny时。 canny滤波器用的最多。 segment_contours_xld 将连续的轮廓进行分段&#xff0c;按圆弧或者执…

软件测试13

Linux命令 1.pwd&#xff1a;查看当前所在的路径位置 2.ls&#xff1a;查看当前路径下有哪些文件 3.cd&#xff1a;切换路径 4.touch&#xff1a;创建普通文件&#xff0c;可以创建单文件&#xff0c;也可以创建多文件&#xff08;touch a&#xff0c;touch b c&#xff09; 5…

【专项训练】高级搜索

高级搜索,这部分非常烧脑,可略过! 包括:剪枝、双向BFS、启发式搜索! 启发式搜索:优先队列,即优先级搜索! 回溯:分治 + 试错 数独问题,类似八皇后! 36. 有效的数独 https://leetcode.cn/problems/valid-sudoku/description/ class Solution(object