调度器的由来
单进程时代的问题
-
单一执行流程,计算机只能一个任务一个任务处理
-
进程阻塞所带来的CPU时间浪费
多进程、多线程的问题
-
设计变得复杂
-
进程/线程的数量越多,切换成本就越大
-
多线程伴随着同步竞争(锁、资源冲突等)
-
-
多进程、多线程的壁垒
-
高内存占用
-
高CPU调度消耗
-
协程的问题
-
N:1
-
无法利用多个CPU
-
出现阻塞的瓶颈
-
-
1:1
-
和多线程/多进程模型无异
-
切换协程成本代价反而变得昂贵
-
-
M:N
-
能够利用多核
-
过于依赖协程调度器的优化和算法
-
早期调度器
M:goroutine协程
N:thread线程
M想要执行、放回G都必须访问全局G队列,并且M有多个,即多线程访问同一资源需要加锁进行保证互斥/同步,所以全局G队列是有互斥锁进行保护的。
问题:
-
创建、销毁、调度G都需要每个M获取锁,这就形成了激烈的锁竞争
-
M转移G会造成延迟和额外的系统负载
-
系统调用(CPU在M之间的切换)导致频繁的线程阻塞和取消阻塞操作增加了系统开销
GMP模型
-
全局队列(Global Queue):存放等待运行的G
-
P的本地队列:同全局队列类似,存放的也是等待运行的G,存的数量有限,不超过256个。新建G’时,G’优先加入到P的本地队列,如果队列满了,则会把本地队列中一半的G移动到全局队列
-
P列表:所有的P都在程序启动时创建,并保存在数组中,最多有
GOMAXPROCS
(可配置)个 -
M:线程想运行任务就得获取P,从P的本地队列获取G,P队列为空时,M也会尝试从全局队列拿一批G放到P的本地队列,或从其他P的本地队列偷一半放到自己P的本地队列。M运行G,G执行之后,M会从P获取下一个G,不断重复下去
P和M何时会被创建?
-
P何时创建:在确定了P的最大数量n后,运行时系统会根据这个数量创建n个P
-
M何时创建:没有足够的M来关联P并运行其中的可运行的G。比如所有的M此时都阻塞住了,而P中还有很多就绪任务,就会去寻找空闲的M,而没有空闲的,就会去创建新的M
参考:
- https://zhuanlan.zhihu.com/p/168610624