Golang调度器GMP学习笔记(一)

调度器的由来

单进程时代的问题

  • 单一执行流程,计算机只能一个任务一个任务处理

  • 进程阻塞所带来的CPU时间浪费

多进程、多线程的问题

  • 设计变得复杂

    1. 进程/线程的数量越多,切换成本就越大

    2. 多线程伴随着同步竞争(锁、资源冲突等)

  • 多进程、多线程的壁垒

    1. 高内存占用

    2. 高CPU调度消耗

协程的问题

  • N:1

    1. 无法利用多个CPU

    2. 出现阻塞的瓶颈

  • 1:1

    1. 和多线程/多进程模型无异

    2. 切换协程成本代价反而变得昂贵

  • M:N

    1. 能够利用多核

    2. 过于依赖协程调度器的优化和算法

早期调度器

M:goroutine协程

N:thread线程

M想要执行、放回G都必须访问全局G队列,并且M有多个,即多线程访问同一资源需要加锁进行保证互斥/同步,所以全局G队列是有互斥锁进行保护的。

Golang调度器GMP学习笔记(一)

问题:

  1. 创建、销毁、调度G都需要每个M获取锁,这就形成了激烈的锁竞争

  2. M转移G会造成延迟和额外的系统负载

  3. 系统调用(CPU在M之间的切换)导致频繁的线程阻塞和取消阻塞操作增加了系统开销

GMP模型

Golang调度器GMP学习笔记(一)

  1. 全局队列(Global Queue):存放等待运行的G

  2. P的本地队列:同全局队列类似,存放的也是等待运行的G,存的数量有限,不超过256个。新建G’时,G’优先加入到P的本地队列,如果队列满了,则会把本地队列中一半的G移动到全局队列

  3. P列表:所有的P都在程序启动时创建,并保存在数组中,最多有GOMAXPROCS(可配置)个

  4. 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

参考:

  1. https://zhuanlan.zhihu.com/p/168610624