0
点赞
收藏
分享

微信扫一扫

GoLang 的协程调度和 GMP 模型


GoLang 的协程调度和 GMP 模型

GoLang 是怎么启动的

关于 GoLang 的汇编语言,请查阅 ​​参考文献[1]​​​ 和 ​​参考文献[2]​​

  1. 编写一个简单的 GoLang 程序 main.go, 用 ​​go build -o main main.go​​ 编译生成可执行文件 main

package main
import "fmt"
func main(){
fmt.Println("Hello, World!")
}

  1. 使用 ​​gdb main​​ 命令调试 main 程序
  2. 输入 ​​info files​​ 命令查看程序入口地址
  3. GoLang 的协程调度和 GMP 模型_系统调用

  4. 用 ​​break​​ 命令给该地址打断点,然后运行程序。发现程序在 ​​_rt0_amd64_linux​​ 函数的 ​​JMP _rt0_amd64(SB)​​ 命令停下。
  5. GoLang 的协程调度和 GMP 模型_参考文献_02

  6. ​_rt0_amd64_linux​​​ 就是入口函数,该函数没有做任何操作直接跳转到 ​​_rt0_amd64(SB)​​,见 ​​参考文献[6]​​
  7. GoLang 的协程调度和 GMP 模型_schedule_03

  8. 继续跳转到 ​​_rt0_amd64​​, 可以看到该函数将存放在栈内的 ​​argc​​ 和 ​​argv​​复制给参数寄存器 DISI 后又跳转到 ​​runtime·rt0_go(SB)​
  9. GoLang 的协程调度和 GMP 模型_系统调用_04

  10. 在 ​​参考文献[8]​​ 中我们可以看到 GoLang 跑起来的主要代码:
  11. GoLang 的协程调度和 GMP 模型_golang_05

  12. (1) 使用 ​​schedinit​​ 始化堆栈,GC 等,根据 ​​GOMAXPROCS​​ 设置创建 P 结构的池子。​​参考文献[10]​​​
    (2) 创建一个新的 G,来执行 ​​runtime.mainPC​​ 函数
    (3) 启动当前的 M, ​​mstart​​ 调用 ​​schedule​​,开始执行之前绑定的 ​​mainPC​​ 所在的 G

GoLang 的 GMP 模型

上一节提到来 GMP 到底是什么意思呢?

  • P(Processor): “处理器”,主要用来限制实际运行的 M 的数量。默认数量跟 CPU 的物理线程数一致,受 ​​GOMAXPROCS​​ 控制。 ​​参考文献[15]​​
  • M(Machine): OS Thread,由 OS 调度。M 的数量不一定。但是处于非阻塞状态的 MP 决定。
    MP 的区别与联系在于:P 是 GoLang 假想的“处理器”,控制实际能够跑起来的 M 的数量。
    如,在 G 的数量无限多的情况下,一开始 MP 的数量一样多。但是当运行在 M 上的 G 调用了同步系统调用阻塞了 M, 此时这个 M 线程是没办法做其他操作的。
    但是 GoLang 想要运行中的 M(OS Thread) 数量是和 P 的数量一样多的,所以它会再创建一个 M(OS Thread) 来跑新的 G。这样就能动态的保证“运行中的 M 的数量等于 P 的总数”
  • G(GoRoutine):协程,应用层看到的“线程”。由 M 调度和执行。
  • LRQ(Local Run Queue): 从定义可知,P 相当于是对 M 的约束,M 只有绑定来 P 才能实际调度和执行 G
    因此,GoLang 给每个 P 设置了一个 LRQ 来维护一个待执行 G 的队列。
    当有 M 绑定在 P 上时,M 就会优先调度和执行该 PLRQ 上的 G
    需要重点关注的一点是:M 才是 OS Thread,因此只有 M 才有执行和调度 G 的能力。而 P 只是一个约束条件。
  • GRQ(Global Run Queue): 没有绑定任何 PG 就会被扔进 GRQ,等待被需要的 P-M 加入 LRQ 执行。

P 的状态

  • Pidle: means a P is not being used to run user code or the scheduler. Typically, it’s on the idle P list and available to the scheduler
  • Prunning: means a P is owned by an M and is being used to run user code or the scheduler.
  • Psyscall: means a P is not running user code, may be stolen by another M.
  • Pgcstop: means a P is halted for STW and owned by the M that stopped the world.
  • Pdead: means a P is no longer used (​​GOMAXPROCS​​​shrank). We reuse ​​P​​​s if ​​GOMAXPROCS​​ increases.

G 的状态:

  • Gidle:just allocated and has not yet been initialized
  • Grunnable:this goroutine is on a run queue
  • Grunning:means this goroutine may execute user code
  • Gsyscall: means this goroutine is executing a system call
  • Gwaiting: means this goroutine is blocked in the runtime(channel)

GMP 的协作

  1. 一般调度:如图,当 M 绑定 P 时,就会开始调度执行 PLRQ 中的 G
    当执行 61 ticks,或者 LRQ 没有 G 时,就会去 GRQ 或者其他的 PLRQG 来执行。
    需要特别注意的是图中 LRQGMPCore 的相对位置关系。M 需要绑定 P 才能调度执行 G, 因此 G 在它俩之间。
  2. GoLang 的协程调度和 GMP 模型_golang_06

  3. 异步系统调用:当 G 需要执行异步系统调用时,M 会把它扔给 NetPoller 管理,然后从 LRQ 中重新取一个 G 来处理。
    NetPoller 通过 ​​epoll​​ 等函数,由监控线程 ​​sysmon​​ 定期询问。当某个 G 的异步系统调用完成后,该 G 就会被扔进 GRQ 中,等待被再次调度。
  4. GoLang 的协程调度和 GMP 模型_golang_07

  5. 同步系统调用:我们知道,同步系统调用阻塞了 M(OS Thread), 该 M 是没有办法为其他 G 提供服务的。此时,该 MP 而言就是没有意义的。(P 的意义是约束运行中的 M 的个数)
    所以,GoLang 会将阻塞的 M - GP 解绑,然后给 P 创建/调度一个新的 M。这样就能保证“运行中的 M 的数量等于 P 的总数”。
    当同步系统调用完成后,阻塞的 M 会将执行系统调用的 G 还回 LRQ。然后该 M 等待销毁或者被重新绑定给某个 P
  6. GoLang 的协程调度和 GMP 模型_系统调用_08

几个重要的函数

  1. runtime.schedule ​​参考文献 [24]​​​
    (1)从 TLS 获取当前正在运行的 G 的信息
    (2)M 是否绑定到当前的 G(同步系统调用)?M 让出绑定的 P ,等待同步系统调用的 G 结束
    (3)GC?STW(stop the word) for GC
    (4)​​当前 P 每执行 61 ticks,从 GRQ 取一定量的 G 加入 LRQ 中​​ (5)从 LRQ 获取可执行的 G
    (6)如果当前的 LRQ 没有 G, 则从 GRQ 或者其他 PLRQG 来调度执行
    (7)如果都没有找到 G,当前 M 让出占用的 P, 进入休眠状态
  2. runtime.mainPC(runtime.main) ​​参考文献[20]​​
  3. GoLang 的协程调度和 GMP 模型_golang_09

  4. (1)限制最大栈大小: Max stack size is 1 GB on 64-bit, 250 MB on 32-bit
    (2)创建一个不需要绑定 PM,,执行 ​​sysmon​​ 函数
    (3)创建 GC goroutine,启动 GC
    (4)运行 ​​package main​​ 的 ​​main​​ 函数
    (5)一系列的收尾操作
  5. runtime.sysmon​​参考文献[21]​​ (1)获取 NetPoller 中已完成操作的 G,将其加入 GRQ
    (2)retake
  • 抢占长时间运行的 G
  • 回收被 ​​syscall​​ 长时间阻塞的 P

参考文献

  • [1] ​​A Quick Guide to Go’s Assembler​​
  • [2] ​​Lecture 4: x86_64 Assembly Language​​
  • [3] ​​GDB info files command​​
  • [4] ​​探索golang程序启动过程​​
  • [5] ​​Compile packages and dependencies​​
  • [6] ​​_rt0_amd64_linux function​​
  • [7] ​​_rt0_amd64 function​​
  • [8] ​​ runtime·rt0_go function​​
  • [9] ​​Notes on the Go 1.4 Run-Time​​
  • [10] ​​runtime.schedinit funtion​​
  • [11] ​​runtime.newproc funtion​​
  • [12] ​​runtime.mstart funtion​​
  • [13] ​​runtime.main funtion​​
  • [14] ​​the definition of G, M, P​​
  • [15] ​​Concurrency, Goroutines and GOMAXPROCS​​
  • [16] ​​The Go scheduler​​
  • [17] ​​Scheduling In Go : Part II - Go Scheduler​​
  • [18] ​​Thread-local storage - Wikipedia​​
  • [19] ​​findrunnable function​​
  • [20] ​​runtime.mainPC​​
  • [21] ​​runtime.sysmon​​
  • [22] ​​runtime.retake​​
  • [23] ​​runtime.preemptone​​
  • [24] ​​runtime.schedule​​


举报

相关推荐

0 条评论