GMP模型详解
前几天上青训营的课时老师在将Go的垃圾回收时提到了GMP模型,但并没有作深入讲解,后面的课似乎也不会在提及了,故参考网上博文以及自己的一些理解来总结一下。在此之前,Goroutine想必学过Go的人都是必知的,它是类似线程一样的可调度单位,也是使得Go享誉天生支持高并发这一名号的缘由。通过创建多个Goroutine来处理任务,将这些Goroutine分配,负载,调度到处理器采用的就是GMP模型。
0 了解Goroutine
Goroutine是golang中实现的协程,是用户级线程。具有以下特点
- 相比线程,启动代价更小,只需要很小的栈空间(2KB左右)
- 栈的大小可以动态伸缩
- 工作在用户态,切换成本小
- 与线程关系是n:m,即可以在n个系统线程上多工调度m个Goroutine
与线程,进程的区别
在引入线程的操作系统中,线程是独立调度的基本单位,并且线程切换与通信的开销要比进程小,同一进程的线程切换不会引起进程切换;进程是独立调度的基本单位,进程切换和通信开销比较大,不同进程的线程切换回引发进程切换。
思考:进程,线程的通信方式?切换流程?可以扩展到协程
1 线程模型
线程创建、管理、调度等采用的方式称为线程模型。线程模型一般分为以下三种:
- 内核级线程(Kernel Level Thread)模型
- 用户级线程(User Level Thread)模型
- 两级线程模型,也称混合型线程模型
他们之间的最大差异在于用户级线程与内核调度实体KSE之间的对应关系。KSE可以理解为内核级线程。用户级线程即协程,由应用程序创建,必须要和内核线程绑定之后才可以执行。
线程由CPU调度是抢占式的,协程由用户调度是协作式的。一个协程让出CPU后,才执行下一个协程
特性 | 用户级线程 | 内核级线程 |
---|---|---|
创建者 | 应用程序 | 内核 |
操作系统是否感知存在 | 否 | 是 |
开销成本 | 创建成本低,上下文切换成本低,上下文切换不需要硬件支持 | 创建成本高,上下文切换成本高,上下文切换需要硬件支持 |
如果线程阻塞 | 整个进程将被阻塞。即不能利用多处理来发挥并发优势 | 其他线程可以继续执行,进程不会阻塞 |
案例 | Java thread, POSIX threads | Window Solaris |
1.1 内核级线程模型
该模型用户线程(协程)和内核线程(线程)是一对一关系(1:1)。线程的创建,销毁,切换工作都是由内核完成。应用程序只能调用编程接口。
优点:
- 多处理器系统中,内核能够并行处理同一进程内的多个线程
- 一个线程被阻塞,不会影响进程运行,可以切换到其他线程继续运行
缺点:
- 线程的创建和删除都需要CPU参与,成本大
1.2 用户级线程模型
协程和线程是多对一的关系。
线程的创建、销毁以及线程之间的协调、同步等工作都是在用户态完成。也就是应用程序的线程库来完成。内核对这些都是无感知的,此时的调度都是基于进程的。
优点:
- 创建,销毁,切换的开销要比内核线程少得多,因为保存线程状态的过程和调用程序都只是本地过程
- 线程能够利用的表空间和堆栈空间比内核级线程多
缺点:
- 线程发生I/O或页面故障引起的阻塞时,如果调用阻塞系统调用则内核由于不知道有多线程的存在,而会阻塞整个进程从而阻塞所有线程, 因此同一进程中只能同时有一个线程在运行
- 资源调度按照进程进行,多个处理机下,同一个进程中的线程只能在同一个处理机下分时复用
1.3 两级线程模型
协程和线程的关系是多对多的关系。充分吸收了上述两个模型的优点,尽量规避缺点。
1.4 Golang线程模型
Golang在底层实现了混合型线程模型。M即系统线程,由系统调用产生,一个M关联一个KSE,即两级线程模型中的系统线程。G为Groutine,即两级线程模型的的应用及线程。M与G的关系是N:M。
2 GMP模型概览
G-M-P分别代表:
- G - Goroutine,Go协程,是参与调度与执行的最小单位
- M - Machine,指的是系统级线程
- P - Processor,指的是逻辑处理器,P关联了的本地可运行G的队列(也称为LRQ),最多可存放256个G。
GMP调度流程大致如下:
- 线程M想运行任务就需要获取P,即与P关联
- 然后从P的本地队列(LRQ)获取G
- 若LRQ中没有可运行的G,M 会尝试从全局队列(GRQ)拿一批G放到P的本地队列
- 若全局队列也未找到可运行的G时候,M会随机从其他 P 的本地队列偷一半放到自己 P 的本地队列。
- 拿到可运行的G之后,M 运行 G,G 执行之后,M 会从 P 获取下一个 G,不断重复下去。
2.1 调度的生命周期
说明:
- runtime 创建最初的线程 m0 和 goroutine g0,并把两者进行关联(g0.m = m0)
- 调度器初始化:设置M最大数量,P个数,栈和内存出事,以及创建 GOMAXPROCS个P
- 示例代码中的 main 函数是 main.main,runtime 中也有 1 个 main 函数 ——runtime.main,代码经过编译后,runtime.main 会调用 main.main,程序启动时会为 runtime.main 创建 goroutine,称它为 main goroutine 吧,然后把 main goroutine 加入到 P 的本地队列。
- 启动 m0,m0 已经绑定了 P,会从 P 的本地队列获取 G,获取到 main goroutine。
- G 拥有栈,M 根据 G 中的栈信息和调度信息设置运行环境
- M 运行 G
- G 退出,再次回到 M 获取可运行的 G,这样重复下去,直到 main.main 退出,runtime.main 执行 Defer 和 Panic 处理,或调用 runtime.exit 退出程序。
2.2 调度的流程状态
可以看出:
- 每个P有个局部队列,局部队列保存待执行的goroutine(流程2),当M绑定的P的的局部队列已经满了之后就会把goroutine放到全局队列(流程2-1)
- 每个P和一个M绑定,M是真正的执行P中goroutine的实体(流程3),M从绑定的P中的局部队列获取G来执行
- 当M绑定的P的局部队列为空时,M会从全局队列获取到本地队列来执行G(流程3.1),当从全局队列中没有获取到可执行的G时候,M会从其他P的局部队列中偷取G来执行(流程3.2),这种从其他P偷的方式称为work stealing
- 当G因系统调用(syscall)阻塞时会阻塞M,此时P会和M解绑即hand off,并寻找新的idle的M,若没有idle的M就会新建一个M(流程5.1)。
- 当G因channel或者network I/O阻塞时,不会阻塞M,M会寻找其他runnable的G;当阻塞的G恢复后会重新进入runnable进入P队列等待执行(流程5.3)
2.3 GMP调度过程中阻塞
GMP模型的阻塞可能发生在下面几种情况:
- I/O,select
- block on syscall
- channel
- 等待锁
- runtime.Gosched()
用户态阻塞
当goroutine因为channel操作或者network I/O而阻塞时(实际上golang已经用netpoller实现了goroutine网络I/O阻塞不会导致M被阻塞,仅阻塞G),对应的G会被放置到某个wait队列(如channel的waitq),该G的状态由_Gruning
变为_Gwaitting
,而M会跳过该G尝试获取并执行下一个G,如果此时没有runnable
的G供M运行,那么M将解绑P,并进入sleep
状态;当阻塞的G被另一端的G2唤醒时(比如channel的可读/写通知),G被标记为runnable
,尝试加入G2所在P的runnext,然后再是P的Local队列和Global队列。
系统调用阻塞
当G被阻塞在某个系统调用上时,此时G会阻塞在_Gsyscall
状态,M也处于 block on syscall
状态,此时的M可被抢占调度:执行该G的M会与P解绑,而P则尝试与其它idle
的M绑定,继续执行其它G。如果没有其它idle
的M,但P的Local队列中仍然有G需要执行,则创建一个新的M;当系统调用完成后,G会重新尝试获取一个idle
的P进入它的Local队列恢复执行,如果没有idle的P,G会被标记为runnable
加入到Global队列。
3 总结
- Golang的线程模型采用混合型线程模型,线程与协程对应关系是N:M
- 每一个M都需要与一个P绑定,P拥有本地可运行G队列,M是执行G的单元,M获取可运行G流程是先从P的本地队列获取,若未获取到,则从其他P偷取过来(即work steal),若其他的P也没有则从全局G队列获取,若都未获取到,则M将处于自旋状态,并不会销毁。
- 当执行G时候,发生通道阻塞等用户级别阻塞时候,此时M不会阻塞,M会继续寻找其他可运行的G,当阻塞的G恢复之后,重新进入P的队列等待执行,若G进行系统调用时候,会阻塞M,此时P会和M解绑(即hand off),并寻找新的空闲的M。若没有空闲的就会创建一个新的M。
- Work Steal和Hand Off保证了线程的高效利用。
G-M-P高效的保证策略有:
- M是可以复用的,不需要反复创建与销毁,当没有可执行的Goroutine时候就处于自旋状态,等待唤醒
- Work Stealing和Hand Off策略保证了M的高效利用
- 内存分配状态(mcache)位于P,G可以跨M调度,不再存在跨M调度局部性差的问题
- M从关联的P中获取G,不需要使用锁,是lock free的