Go GMP模型原理分析

  • Post author:
  • Post category:其他




GMP模型

G: goroutine 表示Go协程,每一个Go关键字都会创建一个协程.

M: machine 表示工作线程,有操作系统调度.

P: processor 表示处理器,包括运行Go代码的必要资源,以及调度goroutine的能力。

M必须持有P才可以执行代码,M会被系统调用阻塞。

P的个数在程序启动时决定,默认等于CPU数量。

M的个数通常稍大于P,因为除了运行Go代码,runtime包还有其他内置任务需要处理。在I/O密集型应用中,可以将GOMAXPROCS设置得大一些。



上图是协程从创建到销毁的完整过程,可以看到每一个P都会绑定一个M和一个本地的G队列。

针对一个某个特定的P,当本地的G队列已满或阻塞的协程G被唤醒,新创建的协程G会加入全局G队列中,P除了会调度本地队列,还会周期性地从全局G队列中摘取协程来调度。



调度策略

  1. 队列轮转

  2. 系统调用

    1. 前面提到M的数量略大于P,多出来的M在G发生系统调用时就可以发挥作用啦,Go提供了一个类似线程池的M的池子(这不就是线程池吗?),当有需要时,从池子中获取,用完就放回池子,不够的话就在创建(这难道还不是线程池吗?可恶)。
    2. 当G0发生系统调用时,M0会释放掉P0,从M池子中拿(或者新建)一个M1加载到P上,继续执行P本地队列中的G,M0因系统调用而阻塞,M1接替M0的工作,只要P不空闲,就能够保证充分利用CPU。
    3. G0结束系统调用,根据M0是否能够获取P,有两种不同的处理方法:

      1. 有空闲的P,M0获取P之后继续执行G0;

      2. 没有空闲的P,G0会放入全局的G队列,而M0将进入缓存池睡眠。
  3. 工作量窃取

    为了解决多个处理器P中G队列的均衡性,Go提供了工作量窃取策略,当P没有需要调度的协程时,会从全局队列中取,要是全局队列中也没有了,就从别的P队列中去偷,一次都取一半。

  4. 抢占式调度

    是指避免某个协程长时间执行,而阻碍其他协程被调度的机制。(有点类似时间片轮转)

    Go1.14之后引入了基于信号的抢占机制,解决了协程在陷入无限循环导致无法被抢占的问题。


    需要特别注意的是,Go runtime在启动程序的时候,会创建一个独立的M作为监控线程称为sysmon,不需要P就可以单独运行


    sysmon的职责

    1. 网络轮询器监控

    2. 系统调用syscall监控

    3. 垃圾回收



版权声明:本文为cxx05260原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。