GPM代表了三个角色,分别是Goroutine、Processor、Machine。
- G — 就是咱们常用的用go关键字创建的执行体,它对应一个结构体g,结构体里保存了goroutine的堆栈信息
- M — 表示操作系统的线程,它由操作系统的调度器调度和管理
- P — 表示处理器,它可以被看做运行在线程上的本地调度器
G
Goroutine 是 Go 语言调度器中待执行的任务,它在运行时调度器中的地位与线程在操作系统中差不多,但是它占用了更小的内存空间,也降低了上下文切换的开销。
Goroutine 只存在于 Go 语言的运行时,它是 Go 语言在用户态提供的线程,作为一种粒度更细的资源调度单元,如果使用得当能够在高并发的场景下更高效地利用机器的 CPU。
M
Go 语言并发模型中的 M 是操作系统线程。调度器最多可以创建 10000 个线程,但是其中大多数的线程都不会执行用户代码(可能陷入系统调用),最多只会有 GOMAXPROCS 个活跃线程能够正常运行。
默认情况下GOMAXPROCS被设置为内核数,假如有四个内核,那么默认就创建四个线程,每一个线程对应一个runtime.m结构体。线程数等于CPU个数的原因是,每个线程分配到一个CPU上就不至于出现线程的上下文切换,可以保证系统开销降到最低。
P
调度器中的处理器 P 是线程和 Goroutine 的中间层,它能提供线程需要的上下文环境,也会负责调度线程上的等待队列,通过处理器 P 的调度,每一个内核线程都能够执行多个 Goroutine,它能在 Goroutine 进行一些 I/O 操作时及时让出计算资源,提高线程的利用率。
因为调度器在启动时就会创建 GOMAXPROCS 个处理器,所以 Go 语言程序的处理器数量一定会等于 GOMAXPROCS,这些处理器会绑定到不同的内核线程上。
调度流程
- P和M一对一关系,每个M关联一个P,M和P的数量与GOMAXPROCS的值一致。
- 创建G时会优先交给P,每个P有个本地队列,最多可容纳256个G,当P本地队列塞满了就会将G放到全局对列中。
- 线程M会从P本地队列获取G执行调度,每调度61次就会去全局队列获取G调度,防止全局队列中的G迟迟无法调度问题。
- 处理器P本地队列的任务G调度完了以后,就会去全局队列获取待执行的任务G,如果全局队列空了,就会去其他处理器P中偷待执行的任务G。
- 如果某个G处于阻塞状态,线程M就会去获取其他任务G执行调度。
- 如果G进行了系统调用syscall,M也会跟着进入系统调用状态,处理器P不会处于等待状态,而是会去找其他比较闲的M执行其他任务G。
举个例子
1 | package main |
打印输出如下:
3
1
2
原理:
此处只有一个线程M和对应一个处理器P,每个处理器P有个待执行的runnext,表示接下来调度的G,创建G时会优先给到runnext,如果继续添加G就会将原来的G挤走到P的本地队列runq(最多256个)中,依此类推2把1挤走,3把2挤走,最后runnext为3,3调度完了从本地队列获取1执行,1执行完了获取2执行,所以得到如上输出结果。.
- 本文标题:GMP调取器模型设计
- 创建时间:2024-02-20 21:33:55
- 本文链接:2024/02/20/golang-gmp/
- 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!