Golang调度模型GMP

142 阅读3分钟

GMP

image.png

  • M:表示操作系统的线程,它是被操作系统管理的线程
  • G:表示Groutine,每一个Goroutine都包含栈堆、指令指针和其它用于调度的重要信息
  • P:表示调度的上下文,它可以被看做一个运行于线程M上的本地调度器

G

Groutine只存在于Go语言运行时,它是Go语言在用户态为我们提供的“线程”,如果一个Groutine由于IO操作而陷入阻塞,操作系统并不会对上下文进行切换,但是Go语言的调度器会将陷入阻塞的Groutine“切换”下去等待系统调用结束并让出计算资源,一种粒度更细的资源调度单元。

Groutine使用名为g的私有结构体表示,该结构体的字段atomicstatus存储了当前Goroutine的状态,Groutine在运行时中定义的状态非常堵,可以将这些状态分为三类:

  • 等待中:表示当前Goroutine等待某些条件满足后才会继续执行
  • 可运行:表示当前Goroutine等待在某个M执行Goroutine的指令
  • 运行中:表示当前Goroutine正在某个M上执行指令

M

Go语言并发模型中的M其实表示的就是操作系统线程,所有Golang程序中最大的“可运行”线程数等于GOMAXPROCS这个变量的值,大多数情况下我们使用Go的默认值,#thread == #cpu,在这种情况下不会触发操作系统级别的线程调度和上下文切换,所有的调度都会发生在用户态,由Go语言调度器触发

操作系统线程在Go语言中使用私有结构体m来表示,该结构体的g0字段是持有调度堆栈的Goroutine(g0 *g),curg是当前线程上运行的Goroutine(curg *g)

P

Go语言调度器中的最后一个重要结构就是处理器P,其实就是线程需要的上下文环境,也是用于处理代码逻辑的处理器,通过处理器P的调度,每一个内核线程M都能够执行多个G,这样就能在G进行一些IO操作时及时对它们进行切换,提高CPU利用率。每一个Go语言程序中处理器数量一定会等于GOMAXPROCS,这是因为调度器在启动时就会创建GOMAXPROCS个处理器P,这些处理器会绑定到不同的线程M上并为它们调度Goroutine

处理器在Go语言运行中同样使用私有结构体p表示;在该结构体字段中,status表示当前处理器状态,runhead、runqtail、runq以及runnext等字段表示处理器持有的运行队列,运行队列中就包含待执行的Groutine列表,状态分为以下几种:

  • _Pidle:处理器没有运行用户代码或者调度器,被空闲队列或者改变其状态的结构体持有,运行队列为空
  • _Prunning:被线程M持有,并且正在执行用户代码或者调度器
  • _Psyscall:没有执行用户代码,当前线程陷入系统调用
  • _Pgcstop:被线程M持有,当前处理器由于垃圾回收被停止
  • _Pdead:当前处理器已经不被使用