Go高阶 の 协程调度器原理
文章目录
一. 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
的本地队列中,如果满了就会放到全局队列中
- 存放等待运行的
-
P列表
- 程序启动时创建
- 最多有
runtime.GOMAXPROCS()
个,可以配置
-
M列表
: 当前操作系统分配到 Go 程序的内核线程数,他不是动态可变的。- m的数量go语言本身限制是 10000 个
- 有一个阻塞,就会创建一个新的
M
- 如果有
M
空闲,那么就会回收或者睡眠
调度器的设计策略
- 复用线程
work stealing 机制
hand off 机制
- 利用并行
GOMAXPROCS
限定 P 的个数CPU核数/2
- 抢占: 抢占可以理解为,每个
CPU
最多分配10ms的时间,每个goroutine
的优先级都是一样的。 - 全局 G 队列:当其他的队列取不到的时候,就从全局队列里取一个,一般情况下,刚加入的
GO
会加入到本地队列里,而不会加入到全局队列中。
偷取工作:
当本线程无可用的 G
时,尝试从其他线程捆绑的 P 偷取 G,而不是销毁线程。
hand off 传球
当本线程因为 G
进行系统调用阻塞时,线程释放捆绑的 P
,把 P
转移给其他空闲的线程执行。
调度器得生命周期
参考文献
[1]https://www.bilibili.com/video/BV19r4y1w7Nx?p=11&share_source=copy_web