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




推荐阅读
  • 本文详细介绍了在 CentOS 7 系统中配置 fstab 文件以实现开机自动挂载 NFS 共享目录的方法,并解决了常见的配置失败问题。 ... [详细]
  • 如何将TS文件转换为M3U8直播流:HLS与M3U8格式详解
    在视频传输领域,MP4虽然常见,但在直播场景中直接使用MP4格式存在诸多问题。例如,MP4文件的头部信息(如ftyp、moov)较大,导致初始加载时间较长,影响用户体验。相比之下,HLS(HTTP Live Streaming)协议及其M3U8格式更具优势。HLS通过将视频切分成多个小片段,并生成一个M3U8播放列表文件,实现低延迟和高稳定性。本文详细介绍了如何将TS文件转换为M3U8直播流,包括技术原理和具体操作步骤,帮助读者更好地理解和应用这一技术。 ... [详细]
  • 基于Net Core 3.0与Web API的前后端分离开发:Vue.js在前端的应用
    本文介绍了如何使用Net Core 3.0和Web API进行前后端分离开发,并重点探讨了Vue.js在前端的应用。后端采用MySQL数据库和EF Core框架进行数据操作,开发环境为Windows 10和Visual Studio 2019,MySQL服务器版本为8.0.16。文章详细描述了API项目的创建过程、启动步骤以及必要的插件安装,为开发者提供了一套完整的开发指南。 ... [详细]
  • 如果应用程序经常播放密集、急促而又短暂的音效(如游戏音效)那么使用MediaPlayer显得有些不太适合了。因为MediaPlayer存在如下缺点:1)延时时间较长,且资源占用率高 ... [详细]
  • 本文将带你快速了解 SpringMVC 框架的基本使用方法,通过实现一个简单的 Controller 并在浏览器中访问,展示 SpringMVC 的强大与简便。 ... [详细]
  • oracle c3p0 dword 60,web_day10 dbcp c3p0 dbutils
    createdatabasemydbcharactersetutf8;alertdatabasemydbcharactersetutf8;1.自定义连接池为了不去经常创建连接和释放 ... [详细]
  • 微信公众号推送模板40036问题
    返回码错误码描述说明40001invalidcredential不合法的调用凭证40002invalidgrant_type不合法的grant_type40003invalidop ... [详细]
  • Java高并发与多线程(二):线程的实现方式详解
    本文将深入探讨Java中线程的三种主要实现方式,包括继承Thread类、实现Runnable接口和实现Callable接口,并分析它们之间的异同及其应用场景。 ... [详细]
  • 如何在Java中使用DButils类
    这期内容当中小编将会给大家带来有关如何在Java中使用DButils类,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。D ... [详细]
  • 在对WordPress Duplicator插件0.4.4版本的安全评估中,发现其存在跨站脚本(XSS)攻击漏洞。此漏洞可能被利用进行恶意操作,建议用户及时更新至最新版本以确保系统安全。测试方法仅限于安全研究和教学目的,使用时需自行承担风险。漏洞编号:HTB23162。 ... [详细]
  • 本文介绍了如何利用Struts1框架构建一个简易的四则运算计算器。通过采用DispatchAction来处理不同类型的计算请求,并使用动态Form来优化开发流程,确保代码的简洁性和可维护性。同时,系统提供了用户友好的错误提示,以增强用户体验。 ... [详细]
  • 深入解析Android 4.4中的Fence机制及其应用
    在Android 4.4中,Fence机制是处理缓冲区交换和同步问题的关键技术。该机制广泛应用于生产者-消费者模式中,确保了不同组件之间高效、安全的数据传输。通过深入解析Fence机制的工作原理和应用场景,本文探讨了其在系统性能优化和资源管理中的重要作用。 ... [详细]
  • 深入剖析Java中SimpleDateFormat在多线程环境下的潜在风险与解决方案
    深入剖析Java中SimpleDateFormat在多线程环境下的潜在风险与解决方案 ... [详细]
  • Android 自定义 RecycleView 左滑上下分层示例代码
    为了满足项目需求,需要在多个场景中实现左滑删除功能,并且后续可能在列表项中增加其他功能。虽然网络上有很多左滑删除的示例,但大多数封装不够完善。因此,我们尝试自己封装一个更加灵活和通用的解决方案。 ... [详细]
  • javascript分页类支持页码格式
    前端时间因为项目需要,要对一个产品下所有的附属图片进行分页显示,没考虑ajax一张张请求,所以干脆一次性全部把图片out,然 ... [详细]
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社区 版权所有