美文网首页
【go笔记】goroutine调度器的GMP模型简介

【go笔记】goroutine调度器的GMP模型简介

作者: 李明燮 | 来源:发表于2022-03-25 16:49 被阅读0次

    说起go语言,离不开goroutine。
    之前使用go语言开发的时候,也没多少机会用到goroutine。
    趁这些天了解一下GMP模型G(goroutine) M(thread) P(Processor)。

    1.GMP模型

    G -> goroutine

    Go中,协程被称为goroutine,一个goroutine只占几KB。
    而且调度也很灵活(是通过runtime调度的)。

    P -> Processor

    它包含了运行goroutine的资源,如果线程想运行goroutine,必须先获取P,P中还包含了可运行的G队列。

    M -> thread

    每个M都代表了1个内核线程,OS调度器负责把内核线程分配到CPU的核上执行。

    协程是程序级别的,  
    协程是通过runtime调度器的Processor分配到thread。
    
    线程thread是操作系统级别的。  
    thread是通过操作系统OS调度器分配到CPU上执行内容。  
    

    看看下图:

    图片备用地址

    gmp

    我们从下往上看每个节点的作用:

    CPU:=> 线程是在CPU中执行。每个CPU一次只能处理一个线程thread。
    M(thread):=> M从P本地队列中获取G执行,执行后从P获取下一个G, 不断的重复下去。
    OS调度器:=> 管理M和CPU之间的调度。
    P(Processor):=> P是M与G之间的协调。所有的P都在程序启动时创建,最多有GOMAXPROCS(可配置)个。P和M是1:1的关系。
    goroutine调度器:=> 管理P和G之间的调度。
    P的本地队列:=> 存放等待运行的G, 新建G时会优先加入到P的本地队列, 如果队列满了, 则会把本地队列中一半的G移动到全局队列。
    全局队列(Global Queue):=> 存放等待运行的G。

    2.Go调度器调度过程解析

    下面分析一下调度器调度的过程。

    ◆func()调度过程

    假设有一个 go func(), 简单的模拟一下调度的过程。

    1. => go func()
    2. => 创建G
    3. => G进入P的本地队列(满了进入全局队列)
    4. => 和P有关联的M通过P获取G(如空的话去别的本地列或全局队列里获取)
    5. => M在CPU中执行。

    ◆main()调度过程

    看下一段代码执行的过程

    package main
    
    import "fmt"
    
    func main() {
        fmt.Println("Hello world")
    }
    
    1. => runtime 创建最初的线程 M0 和 G0,并把2者关联。
    2. => 调度器初始化:初始化 M0、栈、垃圾回收,以及创建和初始化由 GOMAXPROCS设定个数的P和P列表。
    3. => 代码经过编译后,runtime.main会调用main.main,程序启动时会为runtime.main创建goroutine,然后把main goroutine加入到P的本地队列。
    4. => 上述操作中,M0已经绑定了P0,会从P的本地队列获取G,获取到 main goroutine。
    5. => M根据G中的栈信息和调度信息设置运行环境后, M运行G。
    6. => 执行完G后退出,再次回到M获取可运行的G,这样重复下去,直到main.main退出,
      runtime.main执行Defer和Panic处理,或调用runtime.exit退出程序。
      runtime.main的goroutine 运行是调度器的真正开始,直到runtime.main结束。

    这里补充说明一下M0和G0

    M0 是启动程序后的编号为0的主线程,M0对应的实例会在全局变量 runtime.m0 中,不需要在heap上分配,
    M0 负责执行初始化操作和启动第一个 G, 在之后M0就和其他的 M 一样了。

    G0 是每次启动一个M都会第一个创建的 gourtine,G0仅用于负责调度的G,G0不指向任何可执行的函数,每个M都会有一个自己的G0。
    在调度或系统调用时会使用G0的栈空间,全局变量的G0是M0的G0。

    ◆深入解剖调度过程

    看下图:
    刚开始的时候G0会协调要执行的G协程,执行G1的时候有又需要执行G2,这时候优先会放到自己的P本地队列,
    G1执行完后,G0会依次协调执行G2。

    图片备用地址

    gmp

    执行的过程中M如果阻塞了,那P会寻找下一个可执行的M,继续执行下一个G。

    图片备用地址

    gmp

    有2个以上P的时候:
    P2里的本地队列已经没有可执行G了,他就从全局队列中获取可执行G。
    全局队列里没有可执行G时,会从别的P本地队列中偷取可执行G来执行。
    没有可执行G就会进入自选状态等待下一个G。

    图片备用地址

    gmp

    3.结语

    了解这些底层逻辑是为了更好的时候这些功能,
    了解这些goroutine轻便的调度功能,后续写代码的时候多多少少会有一些帮助吧?


    欢迎大家的意见和交流

    email: li_mingxie@163.com

    相关文章

      网友评论

          本文标题:【go笔记】goroutine调度器的GMP模型简介

          本文链接:https://www.haomeiwen.com/subject/tuddjrtx.html