Go 高阶 の GMP 模型
一. Golang协程调度器得由来
1.1多线程和多进程带来的弊端


以单核操作系统为例,根据时间片轮转机制,不同的线程就要不断的切换,那么 线程的数量越多,切换成本也就越大,也就越浪费,同样,多线程随着同步竞争(如锁、竞争资源冲突等),让开发变得越来越复杂
简单点来说,携程就是用户级的多线程。
而且进程和线程占用内存比较大
- 进程占用内存 虚拟内存
4GB - 线程占用内存 约
4MB
所有面临的两个问题,就是 CPU 的高消耗,和 内存的 高 占用。

1.2 Go 怎么做的?
正常的一个线程,是分为用户空间和内核空间的。

我们可以直接把线程的上下两个部分给直接拆开

给他们起个别名,上边的就叫协程,通过协程调度器进行控制协程的切换


二. Goroutine调度器的GMP模型设计思想
GMP 解释
- G:goroutine 协程
- P:processor 处理器
- M:thread 内核线程

-
全局队列:存放等待运行的G -
P的本地队列:- 存放等待运行的
G - 数量限制:不超过 256 G
- 优先将新建的
G放在P的本地队列中,如果满了就会放到全局队列中;这里的本地队列,就是比如G2创建了G3,那么G3就应该加在G2所在的队列中。
- 存放等待运行的
-
P列表- 程序启动时创建
- 最多有
runtime.GOMAXPROCS()个,可以配置
-
M列表: 当前操作系统分配到 Go 程序的内核线程数,他是动态可变的(这里的动态可变,就是指下面阻塞就会创建新的。空闲就会随便或回收)。- m的数量go语言本身限制是 10000 个
- 有一个阻塞,就会创建一个新的
M - 如果有
M空闲,那么就会回收或者睡眠
调度器的设计策略
- 复用线程
work stealing 机制hand off 机制
- 利用并行
GOMAXPROCS限定 P 的个数CPU核数/2
- 抢占: 抢占可以理解为,每个
CPU最多分配10ms的时间,每个goroutine的优先级都是一样的,如果某个G过了 10ms没有执行完,也会被强制剥离cpu,重新加入本地队列。 - 全局 G 队列:当其他的队列取不到的时候,就从全局队列里取一个,一般情况下,刚加入的
GO会加入到本地队列里,而不会加入到全局队列中。
偷取工作
当本线程无可用的 G 时,尝试从其他线程捆绑的 P 偷取 G,而不是销毁线程。因为内核级线程的开辟和销毁时候相当的消耗资源的。

hand off 传球
当本线程因为 G 进行系统调用阻塞时,线程释放捆绑的 P,把 P 转移给其他空闲的线程执行。让这个 G和这个 M 就捆绑在一起阻塞着

当阻塞完成会怎么样?
当阻塞的 M完成的时候,G会从新放到原来的P本地队列或全局队列中,等待被从新调用,而M就会去找他原来的 P 看能不能再要回来,如果不行的,这样话,他就会去P的队列里,有没有等待调用的,如果有,就拿过来,如果没有,这个 M 就加入到 休眠队列中。

调度器得生命周期

参考文献
[1]https://www.bilibili.com/video/BV19r4y1w7Nx?p=11&share_source=copy_web
浙公网安备 33010602011771号