Go的GMP模型

进程、线程、Goroutine

进程是操作系统进行资源调度的基本单位;线程是CPU调度的基本单位,是操作系统可识别的最小执行和调度单位,每个线程完成不同的任务,但是共享同一地址空间;Goroutiune是go实现的一个协程,是一个用户级线程。

进程线程的区别

  1. 一个线程只能属于一个进程,而一个进程可以有多个线程。
  2. 进程在执行过程中拥有独立的内存单元,而多个线程共享进程的内存。
  3. 进程是资源分配的最小单位,线程是CPU调度的最小单位。
  4. 进程切换的开销也远大于线程切换的开销。

线程携程的区别

  1. 相比线程,其启动的代价很小,以很小栈空间启动。
  2. 能够动态地伸缩栈的大小,最大可以支持到Gb级别。
  3. 工作在用户态,切换成很小

线程模型

线程创建、管理、调度等采用的方式称为线程模型。线程模型一般分为以下三种:

  1. 内核级线程(Kernel Level Thread)模型
  2. 用户级线程(User Level Thread)模型
  3. 两级线程模型,也称混合型线程模型

三大线程模型最大差异就在于用户级线程与内核调度实体KSE(KSE,Kernel Scheduling Entity)之间的对应关系。KSE是Kernel Scheduling Entity的缩写,其是可被操作系统内核调度器调度的对象实体,是操作系统内核的最小调度单元,可以简单理解为内核级线程

GMP线程与模型

Golang在底层实现了混合型线程模型。M即系统线程,由系统调用产生,一个M关联一个KSE,即两级线程模型中的系统线程。G为Groutine,即两级线程模型的的应用及线程。M与G的关系是N:M。

Go线程模型与GMP

GMP模型

G-M-P分别代表:

  1. G - Goroutine,Go协程,是参与调度与执行的最小单位
  2. M - Machine,指的是系统级线程
  3. P - Processor,指的是逻辑处理器,P关联了的本地可运行G的队列(也称为LRQ),最多可存放256个G。

GMP模型

GMP调度流程

  1. 线程M要想运行任务,就要先获取P,即M先与P关联
  2. P从本地队列(local p) 中获取G
  3. 若本地队列中没有可供运行的G,M会先尝试从全局队列获取一批G放到P的本地队列
  4. 若全局队列也没有找到可以运行的G,M会随机从其他P的本地队列中偷一半的G放到自己的P本地队列
  5. 拿到可以运行的G后,M先运行G,G执行以后,M会从P中获取下一个G,一直不断重复

GMP调度策略

  1. 复用线程: work stealinghand off 机制保证M线程的高效复用
  2. 利用并行: GOMAXPROCS 设置P的数量,充分利用CPU的多核和m多线程
  3. 抢占:完全公平的时间片轮转,每个G只能占用10ms,防止其他G饿死

线程和协程映射关系

金山办公的后端面试时被问到了,内核线程和协程总是会有一个映射关系的,既然协程是挂在线程上执行的,为何协程会高效呢?底层还是在线程上运行呀。

此刻我们通过总结上面的知识点就能知道答案。

协程只是内核线程上的一个小任务片段 。go通过 GOMAXPROCS() 确保了创建出足够的M队列和P队列,work stealinghand off 机制保证M线程的高效复用

如下附图一张

golang_work_stealing

参考

GMP模型-深入Go语言之旅

Golang 调度器 GMP 原理与调度全分析