GMP调度模型

2021-05-16

协程提高CPU利用率

多进程和多线程已经提高了系统的并发能力,但是为每一个任务创建一个线程的开销会很大 大量的进程和线程会引出 高内存占用以及调度的高消耗。如此引出了协程。

线程分为 内核态线程 和 用户态线程(协程)。 一个 用户态线程必须绑定到一个内核态线程。但是cpu是不知道有用户态线程存在的。它只知道运行内核态线程。 协程跟线程是有区别的,线程由 CPU 调度是抢占式的,协程由用户态调度是协作式的,一个协程让出 CPU 后,才执行下一个协程。 但是在go1.14之后(我记得是这样)改成了基于抢占式信号

协程和内核态线程的绑定关系

N:1关系

N个协程绑定一个线程 优点就是切换的时候在用户态完成 开销很小

1:1关系

这种一对一的关系就是很偷懒的行为 直接绑定到内核态线程 调度交给cpu 但是协程的创建、删除和切换的代价都由 CPU 完成

M:N关系

M个携程绑定一个线程,是N:1 和 1:1的结合,实现起来比较负载,就引出了后来的GMP调度模型 avatar

GM模型

在以前的老版本是没有GMP模型的 P是后来引出的。但是要理解为什么会新增P 就得看看以前的GM模型的缺点 avatar

老调度器没有P 只有一个全局的G 队列 如果对应很多个M(线程)去获取G去消费的话 必然会设计到加锁

  1. 创建、销毁、调度G 都需要每个M获取锁,竞争很大
  2. M转移G会造成延迟和额外的系统负载。比如当前G还在创建G(goroutine中继续创建goroutine) M创建了G为了继续执行是需要把当前创建的G交给其他M
  3. 系统调用(CPU在M直接切换)导致频繁的线程阻塞和取消阻塞操作。

GMP模型

在新的调度器中引入了P (Processor)包含了运行goroutine的资源。如果线程想运行goroutine必须先获取P 也就是每一个M必须绑定一个P 然后每个P会维护一个本地的G队列。并且之前的全局G队列也还是在的 avatar

  1. 全局 队列存放等待运行的G
  2. P的本地队列,存放的也是等待运行的G 但是数量的大小是不超过256个 如果当前G中又创建G 那么会优先加入到当前P的本地队列。如果存不下了就会把本地的一半G放到全局队列
  3. P列表 所有P都在程序启动的时候创建,并保存在数组中。最多又GPMAXPROCS 个 可以在go程序中使用runtime.GOMAXPROCS(num) 配置
  4. M 线程想运行任务就得获取P 从P的本地队列获取G如果P队列为空 M也会尝试从全局队列拿一批G放到P的本地队列。或从其他P的队列中偷一半放到自己队列

Goroutine调度器和 OS(operating system)调度器是通过M结合起来的。每一个M代表的都是一个内核线程。OS调度器负责把内核线程分配到cpu核上去执行。

关于P和M的个数

这个我之前也是傻傻分不清。首先P的数量是由runtime.GOMAXPROCS(num) 它默认是CPU的核数 我是cpu是8核 那么默认就是8

M的数量 最大值是1W 但是内核很难支持这么多线程数 。使用* runtime/debug 中的 SetMaxThreads 函数,设置 M 的最大数量

m和p没有绝对的关系 如果一个m阻塞了 P就会创建或者切换到另外一个M(M也有一个休眠队列)

P和M什么时候被创建

1. 在确认P的最大数量后,就是runtime.GOMAXPROCS (如果没有设置就是默认值 cpu的核数)
2. 没有足够的M来关联P 并运行G的时候 比如所有M都被阻塞了 

调度器的设计策略

复用线程:避免频繁的创建、销毁线程,而是对线程的复用。

1)work stealing 机制

​ 当本线程无可运行的 G 时,尝试从其他线程绑定的 P 偷取 G,而不是销毁线程。

2)hand off 机制

​ 当本线程因为 G 进行系统调用阻塞时,线程释放绑定的 P,把 P 转移给其他空闲的线程执行。

利用并行:GOMAXPROCS 设置 P 的数量,最多有 GOMAXPROCS 个线程分布在多个 CPU 上同时运行。GOMAXPROCS 也限制了并发的程度,比如 GOMAXPROCS = 核数/2,则最多利用了一半的 CPU 核进行并行。

抢占:在 coroutine 中要等待一个协程主动让出 CPU 才执行下一个协程,在 Go 中,一个 goroutine 最多占用 CPU 10ms,防止其他 goroutine 被饿死,这就是 goroutine 不同于 coroutine 的一个地方。

全局 G 队列:在新的调度器中依然有全局 G 队列,但功能已经被弱化了,当 M 执行 work stealing 从其他 P 偷不到 G 时,它可以从全局 G 队列获取 G。

avatar