热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

golang中的GPM到底是什么?

G、P、M三者是golang实现高并发能的最为重要的概念,runtime通过调度器来实现三者的相互调度执行,通过p将用户态的g与内核态资源m的动态绑定来执行,以减少以前通过频繁创建


G、P、M 三者是golang实现高并发能的最为重要的概念, runtime 通过 调度器 来实现三者的相互调度执行,通过p将用户态的 g 与内核态资源 m 的动态绑定来执行,以减少以前通过频繁创建内核态线程而产生的一系列的性能问题,从而发挥服务器最大有限资源的能力。


本节主要通过阅读runtime源码来认识这三个组件到底长的是什么样子,以此加深到 GPM 的理解。go version go1.15.6


G


G是英文字母 goroutine 的缩写,一般称为“ 协程 ”,注意它与线程和进程的区别,这个应该很容易理解,每个goper应该都知道。


每个 Goroutine 对应一个 G 结构体,G 存储 Goroutine 的运行堆栈、状态以及任务函数,可重用。


Goroutine数据结构位于 src/runtime/runtime2.go 文件,注意此文件里有太多重要的底层数据结构,对于我们理解底层runtime非常的重要,建议大量多看看。不需要记住每一个数据结构,但需要的时候要能第一时间想到在哪里查找。


Goroutine 字段非常的多,我们这里分段来理解


type g struct {
// Stack parameters.
// stack describes the actual stack memory: [stack.lo, stack.hi).
// stackguard0 is the stack pointer compared in the Go stack growth prologue.
// It is stack.lo+StackGuard normally, but can be StackPreempt to trigger a preemption.
// stackguard1 is the stack pointer compared in the C stack growth prologue.
// It is stack.lo+StackGuard on g0 and gsignal stacks.
// It is ~0 on other goroutine stacks, to trigger a call to morestackc (and crash).
stack stack // offset known to runtime/cgo
stackguard0 uintptr // offset known to liblink
stackguard1 uintptr // offset known to liblink
}

stack 描述了当前 Goroutine 的栈内存范围 [stack.lo, stack.hi) ,其中stack 的数据结构为


// Stack describes a Go execution stack.
// The bounds of the stack are exactly [lo, hi),
// with no implicit data structures on either side.
// 描述go执行栈
// 栈边界为[lo, hi),左包含可不包含,即 lo≤stack// 两边都没有隐含的数据结构。
type stack struct {
lo uintptr
hi uintptr
}

stackguard0stackguard1 均是一个栈指针,用于扩容场景,前者用于 Go stack ,后者用于C stack。这两个字段主要用于调度器抢占式调度。另外还有三个字段与抢占有关


type g struct {
preempt bool // preemption signal, duplicates stackguard0 = stackpreempt
preemptStop bool // transition to _Gpreempted on preemption; otherwise, just deschedule
preemptShrink bool // shrink stack at synchronous safe point
}

preempt 抢占标记,其值为true 执行 stackguard0 = stackpreempt


preemptStop 将抢占标记修改为 _Gpreedmpted,如果修改失败则取消


preemptShrink 在同步安全点收缩栈


type g struct {
_panic *_panic // innermost panic - offset known to liblink
_defer *_defer // innermost defer
}

_panic 当前Goroutine 中的panic


_defer 当前Goroutine 中的defer


type g struct {
m *m // current m; offset known to arm liblink
sched gobuf
goid int64
}

m 当前 Goroutine 绑定的M,有可能为nil


sched 存储当前 Goroutine 调度相关的数据


goid 当前 Goroutine 的唯一标识,对开发者不可见,一般不使用此字段。可参考相关文章了解为什么Go开发团队为什么不向外开放访问此字段。


gobuf 结构体


type gobuf struct {
// The offsets of sp, pc, and g are known to (hard-coded in) libmach.
// 寄存器 sp,pc和g的偏移量,硬编码在libmach
//
// ctxt is unusual with respect to GC: it may be a
// heap-allocated funcval, so GC needs to track it, but it
// needs to be set and cleared from assembly, where it's
// difficult to have write barriers. However, ctxt is really a
// saved, live register, and we only ever exchange it between
// the real register and the gobuf. Hence, we treat it as a
// root during stack scanning, which means assembly that saves
// and restores it doesn't need write barriers. It's still
// typed as a pointer so that any other writes from Go get
// write barriers.
sp uintptr
pc uintptr
g guintptr
ctxt unsafe.Pointer
ret sys.Uintreg
lr uintptr
bp uintptr // for GOEXPERIMENT=framepointer
}


sp 栈指针


pc 程序计数器


gobuf 主要存储一些寄存器信息,如 sppcg 的偏移量,硬编码在libmach


ctxt 不常见,可能是一个分配在heap的函数变量,因此GC 需要追踪它,不过它有可能需要设置并进行清除,在有
写屏障 的时候有些困难。重点了解一下
write barriers


g 技能当前 gobuf 的 Goroutine


ret 系统调用的结果


bp ??



调度器在将 G 由一种状态变更为另一种状态时,需要将上下文信息保存到这个 gobuf 结构体,当再次运行 G 的时候,再从这个结构体中读取出来,主要用来暂时上下文信息。其中的栈指针和程序计数器会用来存储或者恢复寄存器中的值,改变程序即将执行的代码。


Goroutine 的状态有以下几种( 源码 )
























































状态 描述
_Gidle 0 刚刚被分配并且还没有被初始化
_Grunnable 1 没有执行代码,没有栈的所有权,存储在运行队列中
_Grunning 2 可以执行代码,拥有栈的所有权,被赋予了内核线程 M 和处理器 P
_Gsyscall 3 正在执行系统调用,没有执行用户代码,拥有栈的所有权,被赋予了内核线程 M 但是不在运行队列上
_Gwaiting 4 由于运行时而被阻塞,没有执行用户代码并且不在运行队列上,但是可能存在于 Channel 的等待队列上。若需要时执行ready()唤醒。
_Gmoribund_unused 5 当前此状态未使用,但硬编码在了gdb 脚本里,可以不用关注
_Gdead 6 没有被使用,可能刚刚退出,或在一个freelist;也或者刚刚被初始化;没有执行代码,可能有分配的栈也可能没有;G和分配的栈(如果已分配过栈)归刚刚退出G的M所有或从free list 中获取
_Genqueue_unused 7 目前未使用,不用理会
_Gcopystack 8 栈正在被拷贝,没有执行代码,不在运行队列上
_Gpreempted 9 由于抢占而被阻塞,没有执行用户代码并且不在运行队列上,等待唤醒
_Gscan 10 GC 正在扫描栈空间,没有执行代码,可以与其他状态同时存在


Goroutine 的状态


需要注意的是对于 _Gmoribund_unused 状态并未使用,但在 gdb 脚本中存在;而对于 _Genqueue_unused 状态目前也未使用,不需要关心。


_Gscan 与上面除了 _Grunning 状态以外的其它状态相组合,表示 GC 正在扫描栈。Goroutine 不会执行用户代码,且栈由设置了 _Gscan 位的 Goroutine 所有。
































状态 描述
_Gscanrunnable = _Gscan + _Grunnable // 0x1001
_Gscanrunning = _Gscan + _Grunning // 0x1002
_Gscansyscall = _Gscan + _Gsyscall // 0x1003
_Gscanwaiting = _Gscan + _Gwaiting // 0x1004
_Gscanpreempted = _Gscan + _Gpreempted // 0x1009


Goroutine 的状态


可以看到除了上面提到的两个未使用的状态外一共有14种状态值。许多状态之间是可以进行改变的。如下图所示




goroutine status (
https://github.com/golang-design/Go-Questions )


type g strcut {
syscallsp uintptr // if status==Gsyscall, syscallsp = sched.sp to use during gc
syscallpc uintptr // if status==Gsyscall, syscallpc = sched.pc to use during gc
stktopsp uintptr // expected sp at top of stack, to check in traceback
param unsafe.Pointer // passed parameter on wakeup
atomicstatus uint32
stackLock uint32 // sigprof/scang lock; TODO: fold in to atomicstatus
}


atomicstatus 当前G的状态,上面介绍过G的几种状态值


syscallsp 如果G 的状态为 Gsyscall ,那么值为 sched.sp 主要用于GC 期间


syscallpc 如果G的状态为 GSyscall ,那么值为 sched.pc 同上也是用于GC 期间,由此可见这两个字段是一起使用的


stktopsp 用于回源跟踪,如何理解?


param 唤醒G时传入的参数,如调用
ready()


stackLock 栈锁,什么场景下会使用?



type g struct {
waitsince int64 // approx time when the g become blocked
waitreason waitReason // if status==Gwaiting
}

waitsince G 阻塞时长


waitreason 阻塞原因


type g struct {
// asyncSafePoint is set if g is stopped at an asynchronous
// safe point. This means there are frames on the stack
// without precise pointer information.
asyncSafePoint bool
paniconfault bool // panic (instead of crash) on unexpected fault address
gcscandone bool // g has scanned stack; protected by _Gscan bit in status
throwsplit bool // must not split stack
}

asyncSafePoint 异步安全点;如果 g 在 异步安全点 停止则设置为 true ,表示在栈上没有精确的指针信息


paniconfault 地址异常引起的panic(代替了崩溃)


gcscandone g 扫描完了栈,受状态 _Gscan 位保护


throwsplit 不允许拆分stack 什么意思?


type g struct {
// activeStackChans indicates that there are unlocked channels
// pointing into this goroutine's stack. If true, stack
// copying needs to acquire channel locks to protect these
// areas of the stack.
activeStackChans bool
// parkingOnChan indicates that the goroutine is about to
// park on a chansend or chanrecv. Used to signal an unsafe point
// for stack shrinking. It's a boolean value, but is updated atomically.
parkingOnChan uint8
}

activeStackChans 表示是否有未加锁定的channel指向到了g 栈,如果为true,那么对栈的复制需要channal锁来保护这些区域


parkingOnChan 表示g 是放在chansend 还是 chanrecv。用于栈的收缩,是一个布尔值,但是原子性更新


type g struct {
raceignore int8 // ignore race detection events
sysblocktraced bool // StartTrace has emitted EvGoInSyscall about this goroutine
sysexitticks int64 // cputicks when syscall has returned (for tracing)
traceseq uint64 // trace event sequencer
tracelastp puintptr // last P emitted an event for this goroutine
lockedm muintptr
sig uint32
writebuf []byte
sigcode0 uintptr
sigcode1 uintptr
sigpc uintptr
gopc uintptr // pc of go statement that created this goroutine
ancestors *[]ancestorInfo // ancestor information goroutine(s) that created this goroutine (only used if debug.tracebackancestors)
startpc uintptr // pc of goroutine function
racectx uintptr
waiting *sudog // sudog structures this g is waiting on (that have a valid elem ptr); in lock order
cgoCtxt []uintptr // cgo traceback context
labels unsafe.Pointer // profiler labels
timer *timer // cached timer for time.Sleep
selectDone uint32 // are we participating in a select and did someone win the race?
}

gopc 创建当前G的pc


startpc go func 的pc


waiting 如何理解?


timer 通过time.Sleep 缓存 timer


从字段命名来看,许多字段都与trace 有关,不清楚什么意思


type g struct {
// Per-G GC state
// gcAssistBytes is this G's GC assist credit in terms of
// bytes allocated. If this is positive, then the G has credit
// to allocate gcAssistBytes bytes without assisting. If this
// is negative, then the G must correct this by performing
// scan work. We track this in bytes to make it fast to update
// and check for debt in the malloc hot path. The assist ratio
// determines how this corresponds to scan work debt.
gcAssistBytes int64
}

gcAssistBytes 与GC相关,未理解要表达的意思?


总结



  • 每个G 都有自己的状态,状态保存在 atomicstatus 字段,共有十几种状态值。

  • 每个 G 在状态发生变化时,即 atomicstatus 字段值被改变时,都需要保存当前G的上下文的信息,这个信息存储在 sched 字段,其数据类型为 gobuf ,想理解存储的信息可以看一下这个结构体的各个字段

  • 每个G 都有三个与抢占有关的字段,分别为 preemptpreemptStoppremptShrink

  • 每个 G 都有自己的唯一id, 字段为 goid ,但此字段官方不推荐开发使用

  • 每个 G 都可以最多绑定一个m,如果可能未绑定,则值为 nil

  • 每个 G 都有自己内部的 deferpanic

  • G 可以被阻塞,并存储有阻塞原因,字段 waitsincewaitreason

  • G 可以被进行 GC 扫描,相关字段为 gcscandoneatomicstatus_Gscan 与上面除了 _Grunning 状态以外的其它状态组合)


P


P表示逻辑处理器,对 G 来说,P 相当于 CPU 核,G 只有绑定到 P 才能被调度。对 M 来说,P 提供了相关的执行环境(Context),如内存分配状态(mcache),任务队列(G)等。


P 的数量决定了系统内最大可并行的 G 的数量(前提:物理 CPU 核数  >= P 的数量)。


P 的数量由用户设置的 GoMAXPROCS 决定,但是不论 GoMAXPROCS 设置为多大,P 的数量最大为 256。


P的数据结构也有几十个字段,我们还是分开来理解


type p struct {
id int32
status uint32 // one of pidle/prunning/...
link puintptr
schedtick uint32 // incremented on every scheduler call
syscalltick uint32 // incremented on every system call
sysmontick sysmontick // last tick observed by sysmon
}

id : P的唯一标识


status P当前状态,状态值有_Pidle、_Prunning、_Psyscall、_Pgcstop 和 _Pdead


link 未知


schedtick 每次程序被调用时递增


syscalltick 每次系统调用时时递增


sysmontick sysmon 最后tick的时间,是一个 sysmontick 数据类型。sysmon介绍: https://www.jianshu.com/p/469d0c7a7936


对于P的状态有五种:
































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


P 的状态


M


// TODO




推荐阅读
  • 本文介绍了如何在Azure应用服务实例上获取.NetCore 3.0+的支持。作者分享了自己在将代码升级为使用.NET Core 3.0时遇到的问题,并提供了解决方法。文章还介绍了在部署过程中使用Kudu构建的方法,并指出了可能出现的错误。此外,还介绍了开发者应用服务计划和免费产品应用服务计划在不同地区的运行情况。最后,文章指出了当前的.NET SDK不支持目标为.NET Core 3.0的问题,并提供了解决方案。 ... [详细]
  • 这是原文链接:sendingformdata许多情况下,我们使用表单发送数据到服务器。服务器处理数据并返回响应给用户。这看起来很简单,但是 ... [详细]
  • 本文主要解析了Open judge C16H问题中涉及到的Magical Balls的快速幂和逆元算法,并给出了问题的解析和解决方法。详细介绍了问题的背景和规则,并给出了相应的算法解析和实现步骤。通过本文的解析,读者可以更好地理解和解决Open judge C16H问题中的Magical Balls部分。 ... [详细]
  • 自动轮播,反转播放的ViewPagerAdapter的使用方法和效果展示
    本文介绍了如何使用自动轮播、反转播放的ViewPagerAdapter,并展示了其效果。该ViewPagerAdapter支持无限循环、触摸暂停、切换缩放等功能。同时提供了使用GIF.gif的示例和github地址。通过LoopFragmentPagerAdapter类的getActualCount、getActualItem和getActualPagerTitle方法可以实现自定义的循环效果和标题展示。 ... [详细]
  • 本文介绍了南邮ctf-web的writeup,包括签到题和md5 collision。在CTF比赛和渗透测试中,可以通过查看源代码、代码注释、页面隐藏元素、超链接和HTTP响应头部来寻找flag或提示信息。利用PHP弱类型,可以发现md5('QNKCDZO')='0e830400451993494058024219903391'和md5('240610708')='0e462097431906509019562988736854'。 ... [详细]
  • 在Xamarin XAML语言中如何在页面级别构建ControlTemplate控件模板
    本文介绍了在Xamarin XAML语言中如何在页面级别构建ControlTemplate控件模板的方法和步骤,包括将ResourceDictionary添加到页面中以及在ResourceDictionary中实现模板的构建。通过本文的阅读,读者可以了解到在Xamarin XAML语言中构建控件模板的具体操作步骤和语法形式。 ... [详细]
  • 预备知识可参考我整理的博客Windows编程之线程:https:www.cnblogs.comZhuSenlinp16662075.htmlWindows编程之线程同步:https ... [详细]
  • IjustinheritedsomewebpageswhichusesMooTools.IneverusedMooTools.NowIneedtoaddsomef ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • Whatsthedifferencebetweento_aandto_ary?to_a和to_ary有什么区别? ... [详细]
  • 本文介绍了安全性要求高的真正密码随机数生成器的概念和原理。首先解释了统计学意义上的伪随机数和真随机数的区别,以及伪随机数在密码学安全中的应用。然后讨论了真随机数的定义和产生方法,并指出了实际情况下真随机数的不可预测性和复杂性。最后介绍了随机数生成器的概念和方法。 ... [详细]
  • Apache Shiro 身份验证绕过漏洞 (CVE202011989) 详细解析及防范措施
    本文详细解析了Apache Shiro 身份验证绕过漏洞 (CVE202011989) 的原理和影响,并提供了相应的防范措施。Apache Shiro 是一个强大且易用的Java安全框架,常用于执行身份验证、授权、密码和会话管理。在Apache Shiro 1.5.3之前的版本中,与Spring控制器一起使用时,存在特制请求可能导致身份验证绕过的漏洞。本文还介绍了该漏洞的具体细节,并给出了防范该漏洞的建议措施。 ... [详细]
  • 云原生应用最佳开发实践之十二原则(12factor)
    目录简介一、基准代码二、依赖三、配置四、后端配置五、构建、发布、运行六、进程七、端口绑定八、并发九、易处理十、开发与线上环境等价十一、日志十二、进程管理当 ... [详细]
  • imnewtotheswiftandxcodeworld,soimhavingaproblemtryingtointegrateapackagetomypro ... [详细]
  • TerraformVersionTerraformv0.9.11AffectedResource(s)Pleas ... [详细]
author-avatar
天呀你呀_778
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有