GPM 是什么?

news2025/1/23 17:11:20

G、P、M是Go调度器的三个核心组件,各司其职。在它们精密地配合下,Go调度器得以高效运转,这也是Go天然支持高并发的内在动力。今天这篇文章我们来深入理解GPM模型。

先看G,取goroutine的首字母,主要保存goroutine的一些状态信息以及CPU的一些寄存器的值,例如IP寄存器,以便在轮到本goroutine执行时,CPU知道要从哪一条指令处开始执行。

当goroutine被调离CPU时,调度器负责把CPU寄存器的值保存在g对象的成员变量之中。

当goroutine被调度起来运行时,调度器又负责把g对象的成员变量所保存的寄存器值恢复到CPU的寄存器。

本系列使用的代码版本是1.9.2,来看一下g的源码:

typegstruct{

//goroutine使用的栈
stack       stack   //offsetknowntoruntime/cgo
//用于栈的扩张和收缩检查,抢占标志
stackguard0uintptr//offsetknowntoliblink
stackguard1uintptr//offsetknowntoliblink

_panic         *_panic//innermostpanic-offsetknowntoliblink
_defer         *_defer//innermostdefer
//当前与g绑定的m
m              *m      //currentm;offsetknowntoarmliblink
//goroutine的运行现场
sched          gobuf
syscallsp      uintptr        //ifstatus==Gsyscall,syscallsp=sched.sptouseduringgc
syscallpc      uintptr        //ifstatus==Gsyscall,syscallpc=sched.pctouseduringgc
stktopsp       uintptr        //expectedspattopofstack,tocheckintraceback
//wakeup时传入的参数
param          unsafe.Pointer//passedparameteronwakeup
atomicstatus   uint32
stackLock      uint32//sigprof/scanglock;TODO:foldintoatomicstatus
goid           int64
//g被阻塞之后的近似时间
waitsince      int64  //approxtimewhenthegbecomeblocked
//g被阻塞的原因
waitreason     string//ifstatus==Gwaiting
//指向全局队列里下一个g
schedlink      guintptr
//抢占调度标志。这个为true时,stackguard0等于stackpreempt
preempt        bool     //preemptionsignal,duplicatesstackguard0=stackpreempt
paniconfault   bool     //panic(insteadofcrash)onunexpectedfaultaddress
preemptscan    bool     //preemptedgdoesscanforgc
gcscandone     bool     //ghasscannedstack;protectedby_Gscanbitinstatus
gcscanvalid    bool     //falseatstartofgccycle,trueifGhasnotrunsincelastscan;TODO:remove?
throwsplit     bool     //mustnotsplitstack
raceignore     int8     //ignoreracedetectionevents
sysblocktracedbool     //StartTracehasemittedEvGoInSyscallaboutthisgoroutine
//syscall返回之后的cputicks,用来做tracing
sysexitticks   int64    //cputickswhensyscallhasreturned(fortracing)
traceseq       uint64   //traceeventsequencer
tracelastp     puintptr//lastPemittedaneventforthisgoroutine
//如果调用了LockOsThread,那么这个g会绑定到某个m上
lockedm        *m
sig            uint32
writebuf       []byte
sigcode0       uintptr
sigcode1       uintptr
sigpc          uintptr
//创建该goroutine的语句的指令地址
gopc           uintptr//pcofgostatementthatcreatedthisgoroutine
//goroutine函数的指令地址
startpc        uintptr//pcofgoroutinefunction
racectx        uintptr
waiting        *sudog         //sudogstructuresthisgiswaitingon(thathaveavalidelemptr);inlockorder
cgoCtxt        []uintptr      //cgotracebackcontext
labels         unsafe.Pointer//profilerlabels
//time.Sleep缓存的定时器
timer          *timer         //cachedtimerfortime.Sleep

gcAssistBytesint64
}

源码中,比较重要的字段我已经作了注释,其他未作注释的与调度关系不大或者我暂时也没有理解的。

g结构体关联了两个比较简单的结构体,stack表示goroutine运行时的栈:

//描述栈的数据结构,栈的范围:[lo,hi)
typestackstruct{
//栈顶,低地址
louintptr
//栈低,高地址
hiuintptr
}

Goroutine运行时,光有栈还不行,至少还得包括PC,SP等寄存器,gobuf就保存了这些值:

typegobufstruct{
//存储rsp寄存器的值
sp   uintptr
//存储rip寄存器的值
pc   uintptr
//指向goroutine
g    guintptr
ctxtunsafe.Pointer//thishastobeapointersothatgcscansit
//保存系统调用的返回值
ret  sys.Uintreg
lr   uintptr
bp   uintptr//forGOEXPERIMENT=framepointer
}

再来看M,取machine的首字母,它代表一个工作线程,或者说系统线程。G需要调度到M上才能运行,M是真正工作的人。结构体m就是我们常说的M,它保存了M自身使用的栈信息、当前正在M上执行的G信息、与之绑定的P信息……

当M没有工作可做的时候,在它休眠前,会“自旋”地来找工作:检查全局队列,查看networkpoller,试图执行gc任务,或者“偷”工作。

结构体m的源码如下:

//m代表工作线程,保存了自身使用的栈信息
typemstruct{
//记录工作线程(也就是内核线程)使用的栈信息。在执行调度代码时需要使用
//执行用户goroutine代码时,使用用户goroutine自己的栈,因此调度时会发生栈的切换
g0      *g     //goroutinewithschedulingstack/
morebufgobuf  //gobufargtomorestack
divmod  uint32//div/moddenominatorforarm-knowntoliblink

//Fieldsnotknowntodebuggers.
procid        uint64     //fordebuggers,butoffsetnothard-coded
gsignal       *g         //signal-handlingg
sigmask       sigset     //storageforsavedsignalmask
//通过tls结构体实现m与工作线程的绑定
//这里是线程本地存储
tls           [6]uintptr//thread-localstorage(forx86externregister)
mstartfn      func()
//指向正在运行的gorutine对象
curg          *g       //currentrunninggoroutine
caughtsig     guintptr//goroutinerunningduringfatalsignal
//当前工作线程绑定的p
p             puintptr//attachedpforexecutinggocode(nilifnotexecutinggocode)
nextp         puintptr
id            int32
mallocing     int32
throwing      int32
//该字段不等于空字符串的话,要保持curg始终在这个m上运行
preemptoff    string//if!="",keepcurgrunningonthism
locks         int32
softfloat     int32
dying         int32
profilehz     int32
helpgc        int32
//为true时表示当前m处于自旋状态,正在从其他线程偷工作
spinning      bool//misoutofworkandisactivelylookingforwork
//m正阻塞在note上
blocked       bool//misblockedonanote
//m正在执行writebarrier
inwb          bool//misexecutingawritebarrier
newSigstack   bool//minitonCthreadcalledsigaltstack
printlock     int8
//正在执行cgo调用
incgo         bool//misexecutingacgocall
fastrand      uint32
//cgo调用总计数
ncgocall      uint64      //numberofcgocallsintotal
ncgo          int32       //numberofcgocallscurrentlyinprogress
cgoCallersUseuint32      //ifnon-zero,cgoCallersinusetemporarily
cgoCallers    *cgoCallers//cgotracebackifcrashingincgocall
//没有goroutine需要运行时,工作线程睡眠在这个park成员上,
//其它线程通过这个park唤醒该工作线程
park          note
//记录所有工作线程的链表
alllink       *m//onallm
schedlink     muintptr
mcache        *mcache
lockedg       *g
createstack   [32]uintptr//stackthatcreatedthisthread.
freglo        [16]uint32  //d[i]lsbandf[i]
freghi        [16]uint32  //d[i]msbandf[i+16]
fflag         uint32      //floatingpointcompareflags
locked        uint32      //trackingforlockosthread
//正在等待锁的下一个m
nextwaitm     uintptr     //nextmwaitingforlock
needextram    bool
traceback     uint8
waitunlockf   unsafe.Pointer//todogofunc(*g,unsafe.pointer)bool
waitlock      unsafe.Pointer
waittraceev   byte
waittraceskipint
startingtracebool
syscalltick   uint32
//工作线程id
thread        uintptr//threadhandle

//theseareherebecausetheyaretoolargetobeonthestack
//oflow-levelNOSPLITfunctions.
libcall   libcall
libcallpcuintptr//forcpuprofiler
libcallspuintptr
libcallg  guintptr
syscall   libcall//storessyscallparametersonwindows

mOS
}

再来看P,取processor的首字母,为M的执行提供“上下文”,保存M执行G时的一些资源,例如本地可运行G队列,memeorycache等。

一个M只有绑定P才能执行goroutine,当M被阻塞时,整个P会被传递给其他M,或者说整个P被接管。

//p保存go运行时所必须的资源
typepstruct{
lockmutex

//在allp中的索引
id          int32
status      uint32//oneofpidle/prunning/...
link        puintptr
//每次调用schedule时会加一
schedtick   uint32
//每次系统调用时加一
syscalltickuint32
//用于sysmon线程记录被监控p的系统调用时间和运行时间
sysmontick  sysmontick//lasttickobservedbysysmon
//指向绑定的m,如果p是idle的话,那这个指针是nil
m           muintptr   //back-linktoassociatedm(nilifidle)
mcache      *mcache
racectx     uintptr

deferpool    [5][]*_defer//poolofavailabledeferstructsofdifferentsizes(seepanic.go)
deferpoolbuf[5][32]*_defer

//Cacheofgoroutineids,amortizesaccessestoruntime·sched.goidgen.
goidcache    uint64
goidcacheenduint64

//Queueofrunnablegoroutines.Accessedwithoutlock.
//本地可运行的队列,不用通过锁即可访问
runqheaduint32//队列头
runqtailuint32//队列尾
//使用数组实现的循环队列
runq     [256]guintptr

//runnext非空时,代表的是一个runnable状态的G,
//这个G被当前G修改为ready状态,相比runq中的G有更高的优先级。
//如果当前G还有剩余的可用时间,那么就应该运行这个G
//运行之后,该G会继承当前G的剩余时间
runnextguintptr

//AvailableG's(status==Gdead)
//空闲的g
gfree    *g
gfreecntint32

sudogcache[]*sudog
sudogbuf   [128]*sudog

tracebuftraceBufPtr
traceSwept,traceReclaimeduintptr

pallocpersistentAlloc//per-Ptoavoidmutex

//Per-PGCstate
gcAssistTime     int64//NanosecondsinassistAlloc
gcBgMarkWorker   guintptr
gcMarkWorkerModegcMarkWorkerMode
runSafePointFnuint32//if1,runsched.safePointFnatnextsafepoint

pad[sys.CacheLineSize]byte
}

GPM三足鼎力,共同成就Goscheduler。G需要在M上才能运行,M依赖P提供的资源,P则持有待运行的G。你中有我,我中有你。

描述三者的关系:

在这里插入图片描述

M会从与它绑定的P的本地队列获取可运行的G,也会从networkpoller里获取可运行的G,还会从其他P偷G。

最后我们从宏观上总结一下GPM,这篇文章尝试从它们的状态流转角度总结。

首先是G的状态流转:

在这里插入图片描述

说明一下,上图省略了一些垃圾回收的状态。

接着是P的状态流转:

在这里插入图片描述

通常情况下(在程序运行时不调整P的个数),P只会在上图中的四种状态下进行切换。当程序刚开始运行进行初始化时,所有的P都处于_Pgcstop状态,随着P的初始化(runtime.procresize),会被置于_Pidle

当M需要运行时,会runtime.acquirep来使P变成Prunning状态,并通过runtime.releasep来释放。

当G执行时需要进入系统调用,P会被设置为_Psyscall,如果这个时候被系统监控抢夺(runtime.retake),则P会被重新修改为_Pidle

如果在程序运行中发生GC,则P会被设置为_Pgcstop,并在runtime.startTheWorld时重新调整为_Prunning

最后,我们来看M的状态变化:

在这里插入图片描述

M只有自旋和非自旋两种状态。自旋的时候,会努力找工作;找不到的时候会进入非自旋状态,之后会休眠,直到有工作需要处理时,被其他工作线程唤醒,又进入自旋状态。

本文节选于Go合集《Go 语言问题集》:GOLANG ROADMAP 一个专注Go语言学习、求职的社区。

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

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

相关文章

团结引擎——DotNet Wasm方案

参考:团结引擎 DotNet WebAssembly(Wasm) 介绍 一、当前编译流程 通过IL2CPP将C#转成C/C;通过Emscripen将C/C转成WebAssembly; 二、 当前存在问题 IL2CPP在处理类似泛型、反射结构时,由于缺少运行时信息,必须全量生…

备战蓝桥杯Day18 - 双链表

一、每日一题 蓝桥杯真题之工作时长 这个题写代码做的话很麻烦,而且我也不一定能写出来,所以我直接就是用的excel来计算的时间和。 使用excel的做法 1.先把文件中的时间复制到excel中。 2.把日期和时间分到两列。 分成两列的步骤: 选中要…

让两个电脑通信的方法(TCP连接,UDP连接,C/S架构)

目录 TCP-面向连接UDP-面向无连接C/S架构服务器和客户端的工作过程C/S架构例子 让两个电脑通信的方法是 在C/S的基础上,采用TCP和UDP的方式连接 TCP-面向连接 UDP-面向无连接 C/S架构 服务器和客户端的工作过程 C/S架构例子 服务器与客户端通信的过程类似公司与客户…

【Git教程】(五)分支 —— 并行式开发,分支相关操作(创建、切换、删除)~

Git教程 分支 1️⃣ 并行式开发2️⃣ 修复旧版本中的 bug3️⃣ 分支4️⃣ 当前活跃分支5️⃣ 重置分支指针6️⃣ 删除分支7️⃣ 清理提交对象🌾 总结 对于版本提交为什么不能依次进行,以便形成一条直线型的提交历史记录,我们认为有 以下两个…

在PyCharm中使用Git

安装Git CMD检查Git版本 打开cmd,输入git version,检查当前下载版本 配置git的user信息 在cmd中输入 git config --global user.name "用户名"git config --global user.email "用户邮箱"输入:git config --list&…

自然语言处理: 第十三章Xinference部署

项目地址: Xorbitsai/inference 理论基础 正如同Xorbits Inference(Xinference)官网介绍是一个性能强大且功能全面的分布式推理框架。可用于大语言模型(LLM),语音识别模型,多模态模型等各种模型的推理。通…

安装极狐GitLab Runner并测试使用

本文继【新版极狐安装配置详细版】之后继续 1. 添加官方极狐GitLab 仓库: 对于 RHEL/CentOS/Fedora: curl -L "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.rpm.sh" | sudo bash2. 安装最新版本的极狐G…

node.js 用 xml2js.Parser 读 Freeplane.mm文件,生成测试用例.csv文件

Freeplane 是一款基于 Java 的开源软件,继承 Freemind 的思维导图工具软件,它扩展了知识管理功能,在 Freemind 上增加了一些额外的功能,比如数学公式、节点属性面板等。 编写 mm_xml2js_csv.js 如下 // 用 xml2js.Parser 读 F…

设计模式-代理模式(静态代理,动态代理)

定义 代理模式Proxy是⼀种结构型设计模式,能够增强一些功能,不会影响到之前核心功能的流程。 结构图 1 通过实现接口的方式 2 通过继承类的方式 代理模式与装饰器模式 IT老齐白话设计模式 装饰和代理有着相似的结构, 但是其意图却⾮常…

腾讯云4核8G服务器优惠价格表(轻量+CVM)

腾讯云4核8G服务器多少钱?轻量应用服务器4核8G12M带宽一年446元、646元15个月,云服务器CVM标准型S5实例4核8G配置价格15个月1437.3元,5年6490.44元,标准型SA2服务器1444.8元一年,在txy.wiki可以查询详细配置和精准报价…

DAY12_VUE基本用法详细版

目录 0 HBuilderX酷黑主题修改注释颜色1 VUE1.1 VUE介绍1.2 Vue优点1.3 VUE入门案例1.3.1 导入JS文件1.3.2 VUE入门案例 1.4 VUE基本用法1.4.1 v-cloak属性1.4.2 v-text指令1.4.3 v-html指令1.4.4 v-pre指令1.4.5 v-once指令1.4.6 v-model指令1.4.7 MVVM思想 1.5 事件绑定1.5.1…

C# OpenCvSharp DNN Yolov8-OBB 旋转目标检测

目录 效果 模型信息 项目 代码 下载 C# OpenCvSharp DNN Yolov8-OBB 旋转目标检测 效果 模型信息 Model Properties ------------------------- date:2024-02-26T08:38:44.171849 description:Ultralytics YOLOv8s-obb model trained on runs/DOT…

【.Net 使用阿里云OSS 存储文件】

一、使用NuGet安装【Aliyun.OSS.SDK】 注意:如果有多个项目,需要在具体使用的项目跟启动项目都安装同一版本的Aliyun.OSS.SDK 二、上传代码 using Aliyun.OSS; using System.IO; using System; using CadApplication.Service.Dto; using System.Net; us…

Excel 使用空格或TAB分列

1 选择“数据”>“分列”。 在“文本分列向导”中,选择“分隔符号”>“下一步”。 选择数据的“分隔符”。 例如,“逗号”和“空格”。 可在“数据预览”窗口预览数据。 选择“下一步”,在工作表 目标,在工作表中想显示拆分…

51单片机-(定时/计数器)

51单片机-(定时/计数器) 了解CPU时序、特殊功能寄存器和定时/计数器工作原理,以定时器0实现每次间隔一秒亮灯一秒的实验为例理解定时/计数器的编程实现。 1.CPU时序 1.1.四个周期 振荡周期:为单片机提供定时信号的振荡源的周期…

go环境安装-基于vscode的Windows安装

1、vscode安装 官网链接:https://code.visualstudio.com/ 选择相应的版本,这里选择Windows下的 下载得到一个VSCodeUserSetUp-x64的可执行文件,双击执行,选择要安装的路径,下一步。 2、go语言安装 官网链接&#x…

node.js提取excel中的信息填充到word文件,批量生成合同

1.npm下载 npm i pizzip docxtemplater xlsx 2.excel模板 3.word模板 4.代码 // 引入所需模块 var PizZip require(pizzip); var Docxtemplater require(docxtemplater); var fs require(fs); var path require(path); var xl require(xlsx);// 读取并导出Excel文件 …

langChain学习笔记(待续)

目录 IntroductionLLM的限制扩展理解:什么是机器学习扩展阅读:机器学习的流程 LangChain Introduction LLM的限制 大型语言模型,比如ChatGpt4,尽管已经非常强大,但是仍然存在一些限制: 知识更新&#xff…

开源现场总线协议栈(ethercat、ethernet/ip、opc ua、profinet、canopen、modbus)

ecat主站及其相关: 1.soem:GitHub - OpenEtherCATsociety/SOEM: Simple Open Source EtherCAT MasterSimple Open Source EtherCAT Master. Contribute to OpenEtherCATsociety/SOEM development by creating an account on GitHub.https://github.com/…

ARM系列 -- 虚拟化(一)

今天来研究一个有意思的话题,虚拟化(virtualization)。 开始前,先闲扯一下,最近一个词比较火,“元宇宙(Metaverse)”。在维基百科里面是这么定义元宇宙的,“The Metaver…