GMP调度机制

136 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第13天,点击查看活动详情

1、进程、线程、协程有什么区别?

进程:是应用程序的启动实例,每个进程都有独立的内存空间,不同的进程通过进程间的通信方式来通信。

线程:从属于进程,每个进程至少包含一个线程,线程是 CPU 调度的基本单位,多个线程之间可以共享进程的资源并通过共享内存等线程间的通信方式来通信。

协程:为轻量级线程,与线程相比,协程不受操作系统的调度,协程的调度器由用户应用程序提供,协程调度器按照调度策略把协程调度到线程中运行

2、Goroutine和线程的区别

Goroutine:是用户级别的线程,从内存消耗上来说,一个Goroutine只需要消耗2KB的内存,而创建一个线程需要1MB的栈内存。还要和其他线程之间的内存空间相隔离。在创建和销毁上,由于是用户级别消耗很小,而线程的创建和销毁都需要操作系统操作,是内核级别的,会产生巨大的消耗。最后Goroutine的切换成本比线程要小的多,只保存了三个寄存器,同时切换消耗的时间少,影响执行指令的条数会变小

3、什么是 GMP?

  • G 代表着 goroutine,包含表示goroutine栈的一些字段,指示当前的goroutine的状态,指示当前运行到的指令地址,也就是PC值

  • P 代表着上下文处理器Processor,它维护着一个处于可运行状态的goroutine队列(LRQ)

    • 解决原先只能从全局队列中获取运行的goroutine的全局锁的问题
    • 当一个线程阻塞的时候,可以将和它绑定的P上的goroutine转移到其他线程
  • M 代表 thread 线程,包含正在运行的goroutine等字段

M需要获得P才能运行G。在初始化的时候,Go程序会有初始化的goroutine(垃圾回收,执行调度,运行用户代码),并且会创建出M用来开始G的运行。G会在M上得到执行和调度。

4、调度的核心思想

  1. 重用线程
  2. 限制同时运行的线程数量为N,N为CPU的核心数据
  3. 线程私有可运行的队列,并且可以从其他线程偷取goroutine来运行,阻塞可以传递给其他线程

5、GRQ和LRQ

在 GPM 模型,有一个全局队列(Global Runable Queue):存放等待运行的 G,还有一个 P 的本地队列(Local Runable Queue):也是存放等待运行的 G,但数量有限,不超过 256 个。

GPM 的调度流程:

  • 从 go func()开始创建一个 goroutine,新建的 goroutine 优先保存在 P 的本地队列中,如果 P 的本地队列已经满了,则会保存到全局队列中。
  • M 会从P 的队列中取一个可执行状态的 G 来执行,如果 P 的本地队列为空,就会从全局可运行队列里找;如果还是找不到就从netpoll里找;还是没有,就从其他的 MP 组合偷取一个可执行的 G 来执行,
  • 当 M 执行某一个 G 时候发生系统调用或者M 阻塞,如果这个时候 G 在执行,runtime 会把这个线程 M 从 P 中摘除放到全局可执行队列中,执行其他的goroutine。

6、M 和 P 的数量问题?

关于 G,P,M 的个数问题:

  • G 的个数理论上是无限制的,但是受内存限制
  • P 的数量一般建议是逻辑 CPU 数量的 2 倍
  • M 的数据默认启动的时候是 10000

M与P的数量没有绝对关系,一个M阻塞,P就会去创建或者切换另一个M,所以,即使P的默认数量是1,也有可能会创建很多个M出来