热门标签 | 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




推荐阅读
  • 在Docker中,将主机目录挂载到容器中作为volume使用时,常常会遇到文件权限问题。这是因为容器内外的UID不同所导致的。本文介绍了解决这个问题的方法,包括使用gosu和suexec工具以及在Dockerfile中配置volume的权限。通过这些方法,可以避免在使用Docker时出现无写权限的情况。 ... [详细]
  • eclipse学习(第三章:ssh中的Hibernate)——11.Hibernate的缓存(2级缓存,get和load)
    本文介绍了eclipse学习中的第三章内容,主要讲解了ssh中的Hibernate的缓存,包括2级缓存和get方法、load方法的区别。文章还涉及了项目实践和相关知识点的讲解。 ... [详细]
  • 本文介绍了Windows操作系统的版本及其特点,包括Windows 7系统的6个版本:Starter、Home Basic、Home Premium、Professional、Enterprise、Ultimate。Windows操作系统是微软公司研发的一套操作系统,具有人机操作性优异、支持的应用软件较多、对硬件支持良好等优点。Windows 7 Starter是功能最少的版本,缺乏Aero特效功能,没有64位支持,最初设计不能同时运行三个以上应用程序。 ... [详细]
  • 本文介绍了如何清除Eclipse中SVN用户的设置。首先需要查看使用的SVN接口,然后根据接口类型找到相应的目录并删除相关文件。最后使用SVN更新或提交来应用更改。 ... [详细]
  • IjustinheritedsomewebpageswhichusesMooTools.IneverusedMooTools.NowIneedtoaddsomef ... [详细]
  • mac php错误日志配置方法及错误级别修改
    本文介绍了在mac环境下配置php错误日志的方法,包括修改php.ini文件和httpd.conf文件的操作步骤。同时还介绍了如何修改错误级别,以及相应的错误级别参考链接。 ... [详细]
  • iOS超签签名服务器搭建及其优劣势
    本文介绍了搭建iOS超签签名服务器的原因和优势,包括不掉签、用户可以直接安装不需要信任、体验好等。同时也提到了超签的劣势,即一个证书只能安装100个,成本较高。文章还详细介绍了超签的实现原理,包括用户请求服务器安装mobileconfig文件、服务器调用苹果接口添加udid等步骤。最后,还提到了生成mobileconfig文件和导出AppleWorldwideDeveloperRelationsCertificationAuthority证书的方法。 ... [详细]
  • Java 11相对于Java 8,OptaPlanner性能提升有多大?
    本文通过基准测试比较了Java 11和Java 8对OptaPlanner的性能提升。测试结果表明,在相同的硬件环境下,Java 11相对于Java 8在垃圾回收方面表现更好,从而提升了OptaPlanner的性能。 ... [详细]
  • MySQL数据库锁机制及其应用(数据库锁的概念)
    本文介绍了MySQL数据库锁机制及其应用。数据库锁是计算机协调多个进程或线程并发访问某一资源的机制,在数据库中,数据是一种供许多用户共享的资源,如何保证数据并发访问的一致性和有效性是数据库必须解决的问题。MySQL的锁机制相对简单,不同的存储引擎支持不同的锁机制,主要包括表级锁、行级锁和页面锁。本文详细介绍了MySQL表级锁的锁模式和特点,以及行级锁和页面锁的特点和应用场景。同时还讨论了锁冲突对数据库并发访问性能的影响。 ... [详细]
  • java drools5_Java Drools5.1 规则流基础【示例】(中)
    五、规则文件及规则流EduInfoRule.drl:packagemyrules;importsample.Employ;ruleBachelorruleflow-group ... [详细]
  • ShiftLeft:将静态防护与运行时防护结合的持续性安全防护解决方案
    ShiftLeft公司是一家致力于将应用的静态防护和运行时防护与应用开发自动化工作流相结合以提升软件开发生命周期中的安全性的公司。传统的安全防护方式存在误报率高、人工成本高、耗时长等问题,而ShiftLeft提供的持续性安全防护解决方案能够解决这些问题。通过将下一代静态代码分析与应用开发自动化工作流中涉及的安全工具相结合,ShiftLeft帮助企业实现DevSecOps的安全部分,提供高效、准确的安全能力。 ... [详细]
  • 一句话解决高并发的核心原则
    本文介绍了解决高并发的核心原则,即将用户访问请求尽量往前推,避免访问CDN、静态服务器、动态服务器、数据库和存储,从而实现高性能、高并发、高可扩展的网站架构。同时提到了Google的成功案例,以及适用于千万级别PV站和亿级PV网站的架构层次。 ... [详细]
  • This article discusses the efficiency of using char str[] and char *str and whether there is any reason to prefer one over the other. It explains the difference between the two and provides an example to illustrate their usage. ... [详细]
  • 本文介绍了Redis中RDB文件和AOF文件的保存和还原机制。RDB文件用于保存和还原Redis服务器所有数据库中的键值对数据,SAVE命令和BGSAVE命令分别用于阻塞服务器和由子进程执行保存操作。同时执行SAVE命令和BGSAVE命令,以及同时执行两个BGSAVE命令都会产生竞争条件。服务器会保存所有用save选项设置的保存条件,当满足任意一个保存条件时,服务器会自动执行BGSAVE命令。此外,还介绍了RDB文件和AOF文件在操作方面的冲突以及同时执行大量磁盘写入操作的不良影响。 ... [详细]
  • 本文详细介绍了git常用命令及其操作方法,包括查看、添加、提交、删除、找回等操作,以及如何重置修改文件、抛弃工作区修改、将工作文件提交到本地暂存区、从版本库中删除文件等。同时还介绍了如何从暂存区恢复到工作文件、恢复最近一次提交过的状态,以及如何合并多个操作等。 ... [详细]
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社区 版权所有