GMP模型
G: goroutine 表示Go协程,每一个Go关键字都会创建一个协程.
M: machine 表示工作线程,有操作系统调度.
P: processor 表示处理器,包括运行Go代码的必要资源,以及调度goroutine的能力。
M必须持有P才可以执行代码,M会被系统调用阻塞。
P的个数在程序启动时决定,默认等于CPU数量。
M的个数通常稍大于P,因为除了运行Go代码,runtime包还有其他内置任务需要处理。在I/O密集型应用中,可以将GOMAXPROCS设置得大一些。
上图是协程从创建到销毁的完整过程,可以看到每一个P都会绑定一个M和一个本地的G队列。
针对一个某个特定的P,当本地的G队列已满或阻塞的协程G被唤醒,新创建的协程G会加入全局G队列中,P除了会调度本地队列,还会周期性地从全局G队列中摘取协程来调度。
调度策略
-
队列轮转
-
系统调用
- 前面提到M的数量略大于P,多出来的M在G发生系统调用时就可以发挥作用啦,Go提供了一个类似线程池的M的池子(这不就是线程池吗?),当有需要时,从池子中获取,用完就放回池子,不够的话就在创建(这难道还不是线程池吗?可恶)。
- 当G0发生系统调用时,M0会释放掉P0,从M池子中拿(或者新建)一个M1加载到P上,继续执行P本地队列中的G,M0因系统调用而阻塞,M1接替M0的工作,只要P不空闲,就能够保证充分利用CPU。
-
G0结束系统调用,根据M0是否能够获取P,有两种不同的处理方法:
1. 有空闲的P,M0获取P之后继续执行G0;
2. 没有空闲的P,G0会放入全局的G队列,而M0将进入缓存池睡眠。
-
工作量窃取
为了解决多个处理器P中G队列的均衡性,Go提供了工作量窃取策略,当P没有需要调度的协程时,会从全局队列中取,要是全局队列中也没有了,就从别的P队列中去偷,一次都取一半。 -
抢占式调度
是指避免某个协程长时间执行,而阻碍其他协程被调度的机制。(有点类似时间片轮转)
Go1.14之后引入了基于信号的抢占机制,解决了协程在陷入无限循环导致无法被抢占的问题。
需要特别注意的是,Go runtime在启动程序的时候,会创建一个独立的M作为监控线程称为sysmon,不需要P就可以单独运行
sysmon的职责
1. 网络轮询器监控
2. 系统调用syscall监控
3. 垃圾回收