GPM介绍:
G:goroutine协程
M:thread线程
P:processor处理器
废弃的调度器
M访问、放回G都必须访问全局G队列,M还有多个即多线程访问同一资源需要加锁进行保证互斥/同步,所以全局G队列是有互斥锁进行保护的
老的调度器是有以下缺点的:
1:创建、销毁、调度G时,都需要每个M获得锁,形成了激烈的锁竞争。
2:M转移G会造成延迟和额外的系统负载,例如当前的G创建了新的协程G’,这个G’加入到队列中可能被其他M执行了,因为G和G’是相关的,最好放在同一个M上执行。
3:系统调用(CPU在M之间切换)导致频繁的线程阻塞和取消阻塞操作,这些操作增加了系统开销
新的调度器
processor 包含了运行goroutine的资源,如果M想要运行G,M必须先获取P,P还包含了可运行的G队列
线程是运行goroutine的实体,调度器的功能就是把 可运行的goroutine分配到工作线程上
P的本地队列最大256个,新建 G’ 时优先放到P的本地队列,如果队列满了就把P本地队列的前一半的G加入到全局队列(打乱顺序),此时的 G’ 也会被加入到全局(有个疑问:已经把一半的G移动到全局队列 本地队列有位置了,G’为什么不放入本地队列)
调度策略:
1:复用线程:避免频繁的创建、销毁线程,而是对线程的复用
work stealing机制:当本线程没有可运行的G时,会先从全局队列获取 后从其他P队列偷G,而不是销毁线程
hand off 机制:当本线程因为G进行系统调用发生阻塞时,线程释放绑定的P,把P转移给其他空闲的线程,以便其他的M能获取P而运行
2:利用并行
3:抢占
4:全局G队列
创建G时,运行的G会尝试唤醒其他空闲M以便更多的PM组合来处理G,M去找空闲的P绑定,如果有空闲的P和M 那就组合,此时就是自旋线程了
自旋线程:
1:G唤醒了M,M绑定了P,并运行G0,但P本地队列没有G,M此时为自旋线程
2:当M把P的本地队列中的G都运行完了,全局队列里边也获取不到,其他P的本地队列也偷不到时,此时M也是自旋线程
一旦G0切换到其他的G 自旋状态就消失了
为什么会有自旋线程:
自旋的本质是在运行,线程在运行缺没有执行G,这就浪费了CPU。为啥不销毁现场来节约CPU资源:因为创建和销毁线程也耗CPU资源,系统希望的是当有新的goroutine到来时,立即有M运行它,如果销毁再新建就增加了时延,降低了效率
当然过多的自旋线程也会浪费CPU,所以系统中最多有GOMAXPROCS
个自旋的线程,多余的没事做线程会让他们休眠。
空闲线程(休眠线程):
1:没有得到P绑定的M
当M正在运行G 假如G此时发生了阻塞系统调用,runtime会让M和P解绑,如果P的本地队列有G、全局队列有G并且有空闲的M,P都会立马唤醒1个M和它绑定,否则P会被加入到空闲的P列表等待M来获取
当M正在运行G 假如G此时发生了非阻塞系统调用,跟上边一样PM解绑,但是M会记住这个P,当这个G和M退出系统调用时会尝试获取有记忆的那个P,那个P被别的M占用(P改嫁了)就去P列表获取空闲P,还是获取不到的话,这个G会被记为可运行状态,并加入到全局队列
M因为没有P的绑定而变成休眠状态(长时间休眠等待GC回收销毁)