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

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

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

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

上面这段描述来自公众号“go语言核心编程技术”的调度器系列文章,写得非常好,推荐大家去看,参考资料【阿波张调度器系列教程】可以到达原文。

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

type g struct {   // goroutine 使用的栈   stack       stack   // offset known to runtime/cgo  // 用于栈的扩张和收缩检查,抢占标志  stackguard0 uintptr // offset known to liblink  stackguard1 uintptr // offset known to liblink  _panic         *_panic // innermost panic - offset known to liblink _defer         *_defer // innermost defer   // 当前与 g 绑定的 m  m              *m      // current m; offset known to arm liblink    // goroutine 的运行现场  sched          gobuf    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 // wakeup 时传入的参数    param          unsafe.Pointer // passed parameter on wakeup atomicstatus   uint32   stackLock      uint32 // sigprof/scang lock; TODO: fold in to atomicstatus  goid           int64    // g 被阻塞之后的近似时间 waitsince      int64  // approx time when the g become blocked  // g 被阻塞的原因 waitreason     string // if status==Gwaiting  // 指向全局队列里下一个 g schedlink      guintptr // 抢占调度标志。这个为 true 时,stackguard0 等于 stackpreempt preempt        bool     // preemption signal, duplicates stackguard0 = stackpreempt    paniconfault   bool     // panic (instead of crash) on unexpected fault address preemptscan    bool     // preempted g does scan for gc gcscandone     bool     // g has scanned stack; protected by _Gscan bit in status   gcscanvalid    bool     // false at start of gc cycle, true if G has not run since last scan; TODO: remove? throwsplit     bool     // must not split stack raceignore     int8     // ignore race detection events sysblocktraced bool     // StartTrace has emitted EvGoInSyscall about this goroutine    // syscall 返回之后的 cputicks,用来做 tracing    sysexitticks   int64    // cputicks when syscall has returned (for tracing) traceseq       uint64   // trace event sequencer    tracelastp     puintptr // last P emitted an event for this goroutine   // 如果调用了 LockOsThread,那么这个 g 会绑定到某个 m 上  lockedm        *m   sig            uint32   writebuf       []byte   sigcode0       uintptr  sigcode1       uintptr  sigpc          uintptr  // 创建该 goroutine 的语句的指令地址   gopc           uintptr // pc of go statement that created this goroutine    // goroutine 函数的指令地址    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    // time.Sleep 缓存的定时器    timer          *timer         // cached timer for time.Sleep    gcAssistBytes int64
}

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

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

// 描述栈的数据结构,栈的范围:[lo, hi)
type stack struct { // 栈顶,低地址    lo uintptr  // 栈低,高地址    hi uintptr
}

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

type gobuf struct { // 存储 rsp 寄存器的值 sp   uintptr    // 存储 rip 寄存器的值 pc   uintptr    // 指向 goroutine g    guintptr   ctxt unsafe.Pointer // this has to be a pointer so that gc scans it // 保存系统调用的返回值   ret  sys.Uintreg    lr   uintptr    bp   uintptr // for GOEXPERIMENT=framepointer
}

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

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

结构体 m 的源码如下:

// m 代表工作线程,保存了自身使用的栈信息
type m struct { // 记录工作线程(也就是内核线程)使用的栈信息。在执行调度代码时需要使用 // 执行用户 goroutine 代码时,使用用户 goroutine 自己的栈,因此调度时会发生栈的切换    g0      *g     // goroutine with scheduling stack/  morebuf gobuf  // gobuf arg to morestack    divmod  uint32 // div/mod denominator for arm - known to liblink    // Fields not known to debuggers.   procid        uint64     // for debuggers, but offset not hard-coded    gsignal       *g         // signal-handling g   sigmask       sigset     // storage for saved signal mask   // 通过 tls 结构体实现 m 与工作线程的绑定  // 这里是线程本地存储    tls           [6]uintptr // thread-local storage (for x86 extern register)  mstartfn      func()    // 指向正在运行的 gorutine 对象  curg          *g       // current running goroutine caughtsig     guintptr // goroutine running during fatal signal // 当前工作线程绑定的 p  p             puintptr // attached p for executing go code (nil if not executing go code)   nextp         puintptr  id            int32 mallocing     int32 throwing      int32 // 该字段不等于空字符串的话,要保持 curg 始终在这个 m 上运行 preemptoff    string // if != "", keep curg running on this m    locks         int32 softfloat     int32 dying         int32 profilehz     int32 helpgc        int32 // 为 true 时表示当前 m 处于自旋状态,正在从其他线程偷工作  spinning      bool // m is out of work and is actively looking for work // m 正阻塞在 note 上    blocked       bool // m is blocked on a note    // m 正在执行 write barrier inwb          bool // m is executing a write barrier    newSigstack   bool // minit on C thread called sigaltstack  printlock     int8  // 正在执行 cgo 调用  incgo         bool // m is executing a cgo call fastrand      uint32    // cgo 调用总计数    ncgocall      uint64      // number of cgo calls in total   ncgo          int32       // number of cgo calls currently in progress  cgoCallersUse uint32      // if non-zero, cgoCallers in use temporarily cgoCallers    *cgoCallers // cgo traceback if crashing in cgo call  // 没有 goroutine 需要运行时,工作线程睡眠在这个 park 成员上, // 其它线程通过这个 park 唤醒该工作线程    park          note  // 记录所有工作线程的链表  alllink       *m // on allm schedlink     muintptr  mcache        *mcache   lockedg       *g    createstack   [32]uintptr // stack that created this thread.    freglo        [16]uint32  // d[i] lsb and f[i]  freghi        [16]uint32  // d[i] msb and f[i+16]  fflag         uint32      // floating point compare flags   locked        uint32      // tracking for lockosthread  // 正在等待锁的下一个 m  nextwaitm     uintptr     // next m waiting for lock    needextram    bool  traceback     uint8 waitunlockf   unsafe.Pointer // todo go func(*g, unsafe.pointer) bool   waitlock      unsafe.Pointer    waittraceev   byte  waittraceskip int   startingtrace bool  syscalltick   uint32    // 工作线程 id  thread        uintptr // thread handle  // these are here because they are too large to be on the stack // of low-level NOSPLIT functions.  libcall   libcall   libcallpc uintptr // for cpu profiler   libcallsp uintptr   libcallg  guintptr  syscall   libcall // stores syscall parameters on windows   mOS
}

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

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

// p 保存 go 运行时所必须的资源
type p struct { lock mutex  // 在 allp 中的索引  id          int32   status      uint32 // one of pidle/prunning/... link        puintptr    // 每次调用 schedule 时会加一   schedtick   uint32  // 每次系统调用时加一    syscalltick uint32  // 用于 sysmon 线程记录被监控 p 的系统调用时间和运行时间 sysmontick  sysmontick // last tick observed by sysmon  // 指向绑定的 m,如果 p 是 idle 的话,那这个指针是 nil  m           muintptr   // back-link to associated m (nil if idle)   mcache      *mcache racectx     uintptr deferpool    [5][]*_defer // pool of available defer structs of different sizes (see panic.go)  deferpoolbuf [5][32]*_defer // Cache of goroutine ids, amortizes accesses to runtime·sched.goidgen. goidcache    uint64 goidcacheend uint64 // Queue of runnable goroutines. Accessed without lock. // 本地可运行的队列,不用通过锁即可访问    runqhead uint32 // 队列头  runqtail uint32 // 队列尾  // 使用数组实现的循环队列  runq     [256]guintptr  // runnext 非空时,代表的是一个 runnable 状态的 G, // 这个 G 被 当前 G 修改为 ready 状态,相比 runq 中的 G 有更高的优先级。    // 如果当前 G 还有剩余的可用时间,那么就应该运行这个 G  // 运行之后,该 G 会继承当前 G 的剩余时间    runnext guintptr    // Available G's (status == Gdead)   // 空闲的 g    gfree    *g gfreecnt int32  sudogcache []*sudog sudogbuf   [128]*sudog  tracebuf traceBufPtr    traceSwept, traceReclaimed uintptr  palloc persistentAlloc // per-P to avoid mutex  // Per-P GC state   gcAssistTime     int64 // Nanoseconds in assistAlloc    gcBgMarkWorker   guintptr   gcMarkWorkerMode gcMarkWorkerMode   runSafePointFn uint32 // if 1, run sched.safePointFn at next safe point pad [sys.CacheLineSize]byte
}

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

借用曹大 golang notes 的一幅图,描述三者的关系:

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

参考资料

【阿波张调度器系列教程】http://mp.weixin.qq.com/mp/homepage?_biz=MzU1OTg5NDkzOA==&hid=1&sn=8fc2b63f53559bc0cee292ce629c4788&scene=18#wechatredirect

三足鼎立 —— GPM 到底是什么?(一)相关推荐

  1. 意犹未尽 —— GPM 的状态流转(十)

    最开始的时候,我们讲了 GPM 到底是什么,当时没有看过太多源码,所以对 GPM 没有一个整体上的认识. 现在我们终于到了快要结束的时候,可以从宏观上总结一下 GPM,这篇文章尝试从它们的状态流转角度 ...

  2. 晶圆代工由一家独大,到三足鼎立,再到群雄涿鹿,到底经历了什么?

    在台积电出现之前,基本所有的半导体厂商都有自己的晶圆厂,三星.英特尔.联电等,大厂领衔,小厂林立,半导体市场表面呈现一片祥和,背地里的暗流涌动只有自家最清楚.如果照这个模式继续发展下去,对大部分的芯片 ...

  3. ui设计现状与意义_学UI设计到底好不好?

    很多初学者对UI设计师从事的工作不太了解,对UI设计的发展前景还没摸清楚,就陷入不知道自己能否学会,怎么学才好的困境. 下面小编就带大家一探究竟,来个传说中的UI设计师职业大揭秘,希望能帮助正在犹豫要 ...

  4. CDN行业“三足鼎立”格局已定,谁能代表未来?

    在过去的十几年间,作为互联网行业的一项基础设施, CDN(网络内容分发)行业一直都风平浪静,绝大份市场被网宿.蓝汛等传统CDN厂商牢牢占据.不过,在近几年,行业开始掀起波澜,先是阿里云.腾讯云等云服务 ...

  5. 未明学院:产品经理到底在职场中过得怎么样?

    听说,产品做得不好,产品经理会被"祭天". 听说,开会.撕逼占据了产品经理工作内容的大头? 在行业中,产品经理(即 Product Manager )是负责产品从策划到运营的全过程 ...

  6. Go语言并发原理 | GPM模型

    并发和并行 Go语言并发并行概念 协程 GPM模型 Go语言并发并行概念 首先对于go语言来说分为并发和并行 1.并发:并发是指在很短的时间内完成多个任务,只是宏观上的并发,其实是cpu的切换,对于我 ...

  7. Others6_USB Type-C到底是什么

    1 多功能正反插 苹果让大众认识Type-C "Type-C"这个名称随着苹果全新一代MacBook笔记本电脑的发布而变得人尽皆知,很多人都惊呼"哇!一个接口就可以充当U ...

  8. 八家征信试点机构竟然全部out,央行到底想要一个怎样的市场格局?

    马云曾经表达过这样的观点:"天猫和淘宝无论发展到多么大的规模,他都不用太操心,可蚂蚁金服一点风吹草动都会令他寝食难安,因为搞不好就会被送进监狱" 记者 | 张俊潇 官网 | www ...

  9. 将来会是Python、Java、Golang三足鼎立吗?

    甲:听说最近java跌落神坛,python称霸武林了,你知道吗? 乙:不是吧,我前几天看python怎么还是第三? 丙:你们都在扯蛋,python在2018年就已经是最好的语言了! 乙:不可能吧? 甲 ...

最新文章

  1. RabbitMQ死信队列,延时队列
  2. python下载文件到本地-Python下载网络文本数据到本地内存的四种实现方法示例
  3. Ubuntu ADSL 拨号上网时断时续问题
  4. 以太坊知识教程------智能合约(2)调用 delegatecall call send
  5. MVP:界面与业务逻辑分离在Winform中的应用
  6. Day7: Linux基础片:系统监控
  7. OkHttp之ConnectInterceptor简单分析
  8. Python判断文件是否存在、访问
  9. 中的draw函数_哪一个热图函数更快?
  10. db2exc_971_WIN_x86,db2数据库下载,不是官方下载,直接可下
  11. android我的世界连接pc,我的世界手机玩电脑版操作教程(可以连接pc版服务器)
  12. opensips(1)——安装opensips详细流程
  13. eclipse的优缺点
  14. FFmpeg MP4文件提取音频文件
  15. 程序员专用的简历神器,让你制作简历更简单,方便,专业
  16. AMiner权威发布区块链大数据报告(附下载)
  17. 云存储空间选择十分重要,大小确是关键因素
  18. 解决 XCode6 在 iOS7 系统上出现部分黑屏与不适配问题
  19. 7-301 sdut- C语言实验-数组逆序(数组移位)
  20. Android 图案解锁 9宫格密码解锁

热门文章

  1. Xamarin Live Player Preview 2: 连续运行和调试应用程序
  2. 编程语言分类及python所属类型
  3. Hibernate ORM框架——连接池相关
  4. python2.6 2.7 升级成3.6之后yum
  5. 用BlazeMeter录制JMeter测试脚本
  6. mysql修改密码的注意点
  7. 细谈Ehcache页面缓存的使用
  8. 服务器软RAID和LVM的实现
  9. 联想B450系列安装XP且开启AHCI
  10. FreeBSD 创始人-Jordan Hubbard