go-GMP和Scheduler

GPM模型

在这里插入图片描述

  • G 待执行的goroutine,结构定义在runtime.g

  • M 操作系统中的线程,它由操作系统的调度器 进行 调度和管理, 结构定义在runtime.m

  • P 处理器,是GM的中间件,它通过一个队列绑定了GM,每个P都有一个局部queue,用来存放待执行的本地goroutine

G

Goroutine 只存在于 Go 语言的运行时,它是 Go 语言在用户态提供的线程,作为一种粒度更细的资源调度单元。

Goroutine 在 Go 语言运行时使用私有结构体 runtime.g 表示。这个私有结构体非常复杂,总共包含 40 多个用于表示各种状态的成员变量。

runtime.g结构

runtime.g存放在内存堆上,对所有线程都共享

栈相关字段

type g struct {stack       stackstackguard0 uintptr
}
  • stack: 栈内存范围 [stack.lo, stack.hi)
  • stackguard0:用于调度器抢占式调度,该字段被设置成 StackPreempt 意味着当前 Goroutine 发出了抢占请求;

抢占式调度字段

type g struct {preempt       bool // 抢占信号preemptStop   bool // 抢占时将状态修改成 `_Gpreempted`preemptShrink bool // 在同步安全点收缩栈
}

defer和panic链表字段,链表头插入,链表头获取

type g struct {_panic       *_panic // 最内侧的 panic 结构体_defer       *_defer // 最内侧的延迟函数结构体
}

调度相关

type g struct {m              *msched          gobufatomicstatus   uint32goid           int64
}
  • m:执行当前g的线程m(runtime.m)
  • sched:调度器结构体,里面有全局runq等信息
  • atomicstatus:goroutine的状态
  • goid:协程ID

G状态

状态描述
_Gidle刚被分配,但未被初始化
_Grunnable未执行代码,没有栈的所有权,存储在运行队列中
_Grunning可以执行代码,拥有栈的所有权,被赋予了内核线程M和处理器P
_Gsyscall正在执行系统调用,拥有栈的所有权,没有执行用户代码,被赋予了内核线程M但是不在运行队列上
_Gwaiting由于运行时而被阻塞,没有执行用户代码并且不再运行队列上,但是可能存在于channel的等待队列上
_Gdead没有被使用,没有执行代码,可能有分配的栈
_Gcopystack栈正在拷贝,没有执行代码,不在运行队列上
_Greempted由于抢占而被阻塞,没有执行用户代码并且不在运行队列上,等待唤醒
_GscanGC正在扫描空间,没有执行代码,可以于其他状态同时存在
  • 等待中状态:Goroutine 正在等待某些条件满足,例如:系统调用结束等,包括 _Gwaiting_Gsyscall_Gpreempted 几个状态;
  • 可运行状态:Goroutine 已经准备就绪,可以在线程运行,如果当前程序中有非常多的 Goroutine,每个 Goroutine 就可能会等待更多的时间,即 _Grunnable
  • 运行中状态:Goroutine 正在某个线程上运行,即 _Grunning

G状态转换

img

M

​ Go 语言并发模型中的 M 是操作系统内核线程。调度器最多可以创建 10000 个线程,但是其中大多数的线程都不会执行用户代码(可能陷入系统调用),最多只会有 GOMAXPROCS 个活跃线程能够正常运行(因为运行时需要绑定P,P的数量是由GOMAXPROCS 决定的)。

​ 在默认情况下,一个四核机器会创建四个活跃的操作系统线程,每一个线程都对应一个运行时中的 runtime.m 结构体。

在大多数情况下,我们都会使用 Go 的默认设置,也就是线程数等于 CPU 数,默认的设置不会频繁触发操作系统的线程调度和上下文切换,所有的调度都会发生在用户态,由 Go 语言调度器触发,能够减少很多额外开销。

runtime.m结构

结构体runtime.m表示操作系统线程,这个结构体也包含了几十个字段

与协程相关字段:

type m struct {g0   *gcurg *g...
}
  • g0:每个m都会初始化一个g0,用来切换调度,g0持有调度栈
  • curg:当前执行g(当需要调度其他g时,先切换至g0)

与处理器P相关字段:

type m struct {p             puintptrnextp         puintptroldp          puintptr
}
  • p:m绑定的p,获取本地goroutine
  • nextp:暂存的p,如果M阻塞,当前p会分给其他m,唤醒时就从nextp获取。
  • oldp:切换p后时,把nextp指向当前p,p指针指向新的处理器p

P

​ 调度器中的处理器 P 是线程和 Goroutine 的中间层,它能提供线程需要的上下文环境,也会负责调度线程上的等待队列(runq本地队列),通过处理器 P 的调度,每一个内核线程都能够执行多个 Goroutine,它能在 Goroutine 进行一些 I/O 操作时及时让出计算资源,提高线程的利用率。

因为调度器在启动时就会创建 GOMAXPROCS 个处理器,所以 Go 语言程序的处理器数量一定会等于 GOMAXPROCS,这些处理器会绑定到不同的内核线程上。

runtime.p结构

type p struct {m           muintptrrunqhead uint32runqtail uint32runq     [256]guintptrrunnext guintptr...
}
  • m:p绑定的内核线程m
  • runqhead:本地队列头
  • runqtail:本地队列尾
  • runq:本地队列,用来存放G
  • runnext:下一个要执行的G

P状态

处理器P状态:

状态描述
_Pidle处理器没有运行用户代码或者调度器,被空闲队列或者改变其状态的结构持有,运行队列为空
_Prunning被线程 M 持有,并且正在执行用户代码或者调度器
_Psyscall没有执行用户代码,当前线程陷入系统调用
_Pgcstop被线程 M 持有,当前处理器由于垃圾回收被停止
_Pdead当前处理器已经不被使用

​ 通过分析处理器 P 的状态,我们能够对处理器的工作过程有一些简单理解,例如处理器在执行用户代码时会处于 _Prunning 状态,在当前线程执行 I/O 操作时会陷入 _Psyscall 状态。

P状态转换

调度器设计原理

单线程调度

多线程调度器

任务窃取调度器

抢占式调度器

go服务启动

步骤:

  1. osinit:系统初始化
  2. schedinit:go服务调度器初始化
  3. new main goroutine
  4. mstart:线程启动,开启调度循环,m0开始获取g和创建其他m

schedinit 调度器初始化

变量概念

  • 变量g0存在TLS中):负责调度工作,存放着调度栈信息,每一个m都会有一个自己的g0,g0的协程栈空间是在主线程栈上分配的
  • 变量m0(存在TLS中):程序启动后编号为0的主线程,用于创建P和启动main.main(),之后和其他m一样
  • 全局变量allp[](存在静态数据段):根据环境变量GOMAXPROCS创建N个p,这个切片存放这些p的指针
  • 全局变量allgs[](存在静态数据段):,这个切片存放所有g的指针,存在静态数据段
  • 全局变量allm[](存在静态数据段):记录所有的m,
  • 全局变量sched结构体(存在静态数据段):
    • midle:空闲的m
    • pidle:空闲的p
    • runq:全局的queue队列,用来存放待运行的g

TLS(thread local storage)是每个线程私有的存储空间

静态数据段:是存放全局变量的,是所有thread公用的内存段,所以需要加锁来保证线程安全

初始化步骤

每一个m创建时都会进行调度器初始化。以下是第一个m0的初始化步骤:

  1. 创建g0、m0,他们相互存着对方的指针是1:1绑定的
  2. m0根据环境变量GOMAXPROCS创建N个p,绑定allp[]全局变量
  3. 通过指针将m0和处理器allp[0]绑定
  4. 将allp[0]以外的处理器设置成__Pidel__状态
  5. 创建了一些全局变量allgs[]allm[]sched

new main goroutine

调用newproc()方法来创建main函数的协程,加入到m0本地队列的P中

在这里插入图片描述

创建goroutine

以下面例子做协程创建

package main func helle(name string) {fmt.Println("Hello ", name)
}func main() {name := "Goroutine"go hello(name)	// 调用newproc()创建协程
}

以下是代码函数栈帧的变化,栈内变量是由高到底存放“

  1. 执行main函数,创建main函数的函数栈帧:

    1. 函数返回地址addr
    2. 调用者main的栈基,BP of main
  2. 执行name:="Goroutine",存放调用者caller(main函数)的局部变量

  3. 执行go hello(name),准成机器码就是执行newproc(siz int32, fn *funcval)。变量由右至左存放到栈 siz变量, fn变量 和 参数变量name。main函数栈帧结束

  4. 调用newproc的返回地址(调用一个函数叫做call func),指向hello()

  5. 开始newprocd的栈基,BP of newproc

  6. 切换到g0栈(线程里的栈帧)。调用newproc1()函数。因为线程栈比协程栈要大,防止栈溢出

    在这里插入图片描述

  7. newproc1()会先将当前m锁住,runtime.gfget方法获取过两种不同的方式获取新的 runtime.g

    • 从 Goroutine 所在处理器的 gFree 列表或者调度器的 sched.gFree 列表中获取 runtime.g);空协程,可是已经分配了栈内存空间,避免g0的切换和g的重复销毁/创建
    • 调用 runtime.malg生成一个新的 runtime.g并将结构体追加到全局的 Goroutine 列表 allgs 中。runtime.g结构体保存在堆上(因为要线程共享),runtime.g.stack指向协程函数funcval的函数栈帧。

mstart 开启调度循环

  1. 启动调度器。Go 语言运行时会调用 runtime.mstart 以及 runtime.mstart1,前者会初始化 g0 的 stackguard0stackguard1 字段,后者会初始化线程并调用 runtime.schedule 进入调度循环。
  2. 查找可运行协程。runtime.schedule会调用runtime.findrunnabel,阻塞查找goroutine,通过以下的过程获取可运行的 Goroutine:
    • 从本地运行队列、全局运行队列中查找;
    • 从网络轮询器中查找是否有 Goroutine 等待运行;
    • 通过 runtime.runqsteal 尝试从其他随机的处理器中窃取待运行的 Goroutine,该函数还可能窃取处理器的计时器;
  3. 运行协程runtime.execute 执行获取的 Goroutine,做好准备工作后,它会通过 runtime.gogo 将 Goroutine 调度到当前线程上。
  4. 结束协程runtime.goexit0 函数,该函数会将 Goroutine 转换会 _Gdead 状态、清理其中的字段、移除 Goroutine 和线程的关联并调用。重新加入处理器的 Goroutine 空闲列表 gFree。返回1步骤重新一轮新的调度

触发调度器

主动挂起

  1. runtime.gopark切换到g0触发调度是最常见的方法,他会将正在运行的goroutine暂停,不会扔回runq,状态从_Gruning变成_GWaiting
  2. 当满足特定条件后,调用runtime.goready将协程从_Gwaiting状态切换成_Grunable加入到本地队列

系统调用

系统调用也会触发运行时调度器的调度,为了处理特殊的系统调用,Goroutine 中加入了 _Gsyscall 状态。系统调用会让M和P分离,释放P,让其他空闲M绑定P。

准备工作

runtime.entersyscall 会在获取当前程序计数器和栈位置之后调用 runtime.reentersyscall,它会完成 Goroutine 进入系统调用前的准备工作:

  1. 禁止线程上发生的抢占,防止出现内存不一致的问题;
  2. 保证当前函数不会触发栈分裂或者增长;
  3. 保存当前的程序计数器 PC 和栈指针 SP 中的内容;
  4. 将 Goroutine 的状态更新至 _Gsyscall
  5. 将 Goroutine 的处理器和线程暂时分离并更新处理器的状态到 _Psyscall
  6. 释放当前线程上的锁;

恢复工作

当系统调用结束后,会调用退出系统调用的函数 runtime.exitsyscall 为当前 Goroutine 重新分配资源,该函数有两个不同的执行路径:

  1. 调用 runtime.exitsyscallfast:获取P的方式来恢复
    1. 如果原处理器任然处于_Psyscall,直接使用原处理器
    2. 如果原处理器被其他M绑定,去sched.pidle获取一个空闲p。
  2. 切换至调度器的 Goroutine 并调用 runtime.exitsyscall0:此方法是无法获取到可用P才会调用,主要就是将G状态变成_Grunable,扔到全局变量runq,由调度器去处理

协作式调度

Go 语言基于协作式和信号的两种抢占式调度,这里主要介绍其中的协作式调度。runtime.Gosched 函数会主动让出处理器,允许其他 Goroutine 运行。该函数无法挂起 Goroutine,调度器可能会将当前 Goroutine 调度到其他线程上。

最终在 g0 的栈上调用 runtime.goschedImpl,运行时会更新 Goroutine 的状态到 _Grunnable,让出当前的处理器并将 Goroutine 重新放回全局队列,在最后,该函数会调用 runtime.schedule 触发调度。

线程的生命周期

Go 语言的运行时会通过 runtime.startm 启动线程来执行处理器 P,如果我们在该函数中没能从闲置列表中获取到线程 M 就会调用 runtime.newm 创建新的线程:

clone 创建的线程会在线程主动调用 exit、或者传入的函数 runtime.mstart 返回会主动退出,runtime.mstart 会执行调用 runtime.newm 时传入的匿名函数 fn,到这里也就完成了从线程创建到销毁的整个闭环。

巨人肩膀

Golang调度器GMP原理与调度全分析

Go语言设计与实现

幼麟go系列

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

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

相关文章

Vue.js新手指南:从零开始建立你的第一个应用

🌷🍁 博主猫头虎(🐅🐾)带您 Go to New World✨🍁 🦄 博客首页——🐅🐾猫头虎的博客🎐 🐳 《面试题大全专栏》 🦕 文章图文…

编程获取图像中的圆半径

版权声明:本文为博主原创文章,转载请在显著位置标明本文出处以及作者网名,未经作者允许不得用于商业目的。 即将推出EmguCV的教程,请大家还稍作等待。 之前网友咨询如何获得图像中圆形的半径,其中有两个十字作为标定…

Kotlin文件遍历FileTreeWalk filter

Kotlin文件遍历FileTreeWalk filter import java.io.Filefun main(args: Array<String>) {val filePath "."val file File(filePath)val fileTree: FileTreeWalk file.walk()fileTree//.maxDepth(1) //遍历层级1&#xff0c;不检查子目录.filter {it.isFile…

中小企业建设数字化工厂,选择集成还是重构

随着科技的飞速发展和市场竞争的日益激烈&#xff0c;数字化工厂管理系统已成为中小企业未来发展的必经之路。然而&#xff0c;对于许多中小企业来说&#xff0c;建设数字化工厂并非易事。在建设数字化工厂的过程中&#xff0c;企业需要面对许多问题&#xff0c;其中最关键的问…

如何使用 RunwayML 进行创意 AI 创作

标题&#xff1a;如何使用 RunwayML 进行创意 AI 创作 介绍 RunwayML 是一个基于浏览器的人工智能创作工具&#xff0c;可让用户使用各种 AI 功能来生成图像、视频、音乐、文字和其他创意内容。RunwayML 的功能包括&#xff1a; * 图像生成&#xff1a;使用生成式对抗网络 (…

laravel框架 - 开发实战(目录结构,路由,控制器,模型,视图)

一、laravel框架的目录结构 app:应用目录&#xff0c;保存项目中的控制器、模型等 bootstrap:保存框架启动的相关文件 config:配置文件目录 database:数据库迁移文件和数据填充文件 public:应用入口文件index.php和前端资源文件&#xff08;如CSS、JavaScript等&#xff09…

VEX —— Attribute type metadata

Houdini几何体属性有一些元数据metadata&#xff0c;用于指定属性中的数据是否表示某种变换transformation&#xff08;如位置或旋转&#xff09;&#xff0c;及几何体本身被变换时是否或如何被修改&#xff1b; Houdini理解以下信息类型值&#xff1a; “none”&#xff0c;无…

SQL 2008 R2 和vCenter 5.1安装步骤与AQ

准备情况&#xff1a; Windows 2008 r2 sp1 64bit操作系统 Sql 2008 完整版安装包&#xff08;名称&#xff1a;SQLFULL_CHS.iso 安装完成会安装managment&#xff09; vCenter完整版安装包&#xff08;名称&#xff1a;VMware-VIMSetupall-5.1.0-799735.iso&#xff09; …

Matlab图像处理-HSV

HSV HSV(色调、饱和度、数值)是人们从颜色轮或调色板中挑选颜色(即颜料或油墨)时所用的几种彩色系统之一。这种彩色系统与RGB系统相比&#xff0c;更加接近于人们的经验和描述彩色感觉时所用的方式。在艺术领域&#xff0c;色调、饱和度和数值分别称为色泽、明暗和调色。 HSV…

无涯教程-JavaScript - IFS函数

描述 IFS函数检查是否满足一个或多个条件,并返回与第一个TRUE条件相对应的值。此功能已在Excel 2016中添加。 语法 IFS (logical_test1, value_if_true1, [logical_test2, value_if_true2], [logical_test3, value_if_true3]…) 争论 Argument描述Required/Optionallogical…

短视频seo矩阵系统源码开发搭建--代用户发布视频能力

短视频SEO矩阵系统源码开发搭建的代用户发布视频能力&#xff0c;主要是指在系统平台上&#xff0c;允许用户将其创作的内容发布到指定的账号或平台&#xff0c;并设置好相关的标题、话题、锚点等信息。 一、搭建步骤及注意事项 确定使用场景。根据业务需求&#xff0c;确定该…

2022年CCF-CSP考前冲刺

202212-1现值计算 思路&#xff1a; 本题很简单&#xff0c;按照题目所给条件输入输出就行。 注意有效数字。 代码&#xff1a; #include<bits/stdc.h> using namespace std; const int N1010; int n; double i; int q[N]; double all;int main(){cin>>n>>…

山洪灾害预警方案(山洪预警解决方案的组成)

​ 随着气候变化的不断加剧&#xff0c;山洪灾害在许多地区成为了极具威胁性的自然灾害之一。为了帮助地方政府和居民更好地预防和应对山洪灾害&#xff0c;我们设计了一套基于星创易联的SR600工业路由器和DTU200的山洪灾害预警方案&#xff0c;并成功在某地区进行了部署。 案…

Tomcat修改配置文件

1.Tomcat启动乱码问题 1.1conf目录 说明&#xff1a;找到conf目录下logging.properties文件 1.2打开logging.properties文件 说明&#xff1a;将UTF-8修改成GBK 1.3.效果 2.端口冲突问题 2.1 conf目录 2.2打开 server.xml文件 2.3.修改端口 说明&#xff1a;port指的就是端…

机器视觉Halcon-焊点提取排序设计思路一

目录 一.内容提要①本文是Blob示例之一,利用二值化原理阈值分割的方法,进行焊点检出的思路。二.问题分析及设计思路①.对图形窗口字体设置②.通过亮背景提取暗特征,提取焊点③.图像处理④.显示排序数字一.内容提要 ①本文是Blob示例之一,利用二值化原理阈值分割的方法,进…

嵌入式-数据进制之间的转换

目录 一.简介 1.1十进制 1.2二进制 1.3八进制 1.4十六进制 二.进制转换 2.1二进制-十进制转换 2.2八进制-十进制转换 2.3十六进制-十进制转换 2.4十进制-二进制转换 2.5十进制-八进制转换 2.6十进制-十六进制转换 2.7小数部分转换 一.简介 被传入到计算机的数据要…

5G定位技术原理与应用场景

5G高精度定位服务不仅适用于应急&#xff0c;还可以支持大量的个人业务&#xff0c;包括室外和室内场景&#xff0c;可以在交通道路、隧道、地下停车场或室内环境中支持高精度定位服务。在这些区域中&#xff0c;由于卫星信号的覆盖范围较小&#xff0c;基于5G网络的高精度定位…

定时器类的编写与解析 —— TinyWebServer

定时器类的编写与解析 —— TinyWebServer 一、前言 定时器非常好写。就是链表加定时函数。搞懂他的作用就成。 定时器的作用是什么&#xff1f;什么是回调函数&#xff1f;用到的函数是什么&#xff1f; 二、问题回答 Ⅰ、定时器的作用是什么&#xff1f; 处理非活跃的连…

基于ssm的蛋糕预定网站

项目描述 临近学期结束&#xff0c;还是毕业设计&#xff0c;你还在做java程序网络编程&#xff0c;期末作业&#xff0c;老师的作业要求觉得大了吗?不知道毕业设计该怎么办?网页功能的数量是否太多?没有合适的类型或系统?等等。这里根据疫情当下&#xff0c;你想解决的问…

强强/视频SDK:VisioForge SDKs .Net 15.6.8 Crack

VisioForge 为软件开发人员提供视频捕获、编辑和播放解决方案 使用我们的开发人员软件&#xff0c;您可以开发用于从多种来源&#xff08;例如网络摄像头、IP 摄像机、摄像机或 PC 屏幕&#xff09;捕获视频的应用程序。视频可以保存为所有最流行格式的视频文件&#xff0c;例如…