美文网首页
GO基础学习(9)GMP模型-GMP

GO基础学习(9)GMP模型-GMP

作者: 温岭夹糕 | 来源:发表于2023-04-28 23:17 被阅读0次

写在开头

非原创,知识搬运工/整合工,仅用于自己学习,本文主要介绍GMP和几个核心结构体

往期回顾

  1. 基本数据类型
  2. slice/map/array
  3. 结构体
  4. 接口
  5. nil
  6. 函数
  7. 程序到进程
  8. 进程到线程再到协程
  9. GO调度器

带着问题去阅读

1.runtime

复习一下上一章节的内容 image.png

GO程序执行由两层组成,分别是用户程序和运行时runtime,runtime直接和内核打交道,实际上go的runtime包含4个模块:

  • GO Scheduler,负责调度管理协程(GMP)
  • netpoll,网络轮询
  • memory 内存分配
  • garbage 垃圾回收

1.1 GMP模型

G - goroutine.
M - worker thread, or machine
P - processor, a resource that is required to execute Go code.
M must have an associated P to execute Go code, however it can be blocked or in a syscall w/o an associated P.
这是runtime.proc.go对GMP的描述

在同一时间点上,一个处理器CPU的一个核只能执行一个线程任务,我们把P看作是一个虚拟的核,它是个抽象的概念,那么核上运行的线程就是M,协程G要依赖线程才能执行。
我们前面学过现实中线程核协程是M:N的关系,即可以有M个线程,每个线程上有N个协程,但是线程是给协程分配资源,最后是CPU核执行的。但是在GMP模型中,最后的执行者是线程M,这需要区分,处理器核P只是用来存放可以被运行的G,M从P中获取G,协程才得以执行(最初是没有P这个概念,直接从全局队列获取G)。
GO程序启动后,会给每一个CPU核分配一个P

runtime.NumCPU() 

该函数返回当前进程可以用到的核数量,不是物理核心数,
分配好P后,再给每一个P分配一个M,但是这些M仍由操作系统调度器OS Scheduler调度(G才是用户调度的),G在M上执行,M在CPU核(这里指实际的核,不是P)上调度,G也是在M上调度的

1.2 运行队列

我们前面提到了两个队列,一个是全局队列GRQ(现在还有),另一个是私有队列LRQ(P上私有),全局队列上的G还没有分配到具体的P image.png

1.3 协程的状态

协程和线程类似,有三种状态:

  • Waiting 等待状态,如g被阻塞等待某个时间发生,典型的如等待网络数据
  • Runable 就绪状态,只要给M就能直接运行
  • Executing 运行状态,正在M上执行指令

1.4协程的调度时机

在四种情况下,G可能发生调度,也不一定会发生(有机会被调度)

  • 使用关键词go 创建一个新G,调度器会考虑调度
  • GC 前一节学过,GC垃圾回收是程序开始会创建的一个G,也要在M上运行,触发GC意味着要调度,处理堆上的内存
  • 系统调用 当G进行系统调用时会被阻塞等待,还记得协程调度器的核心目的吗?榨干线程M,那自然不允许它浪费,调走该G
  • 内存同步访问 如atomic,mutex,channel操作都会阻塞G,本质上也是系统调用

1.5同步/异步系统调用

这里指G进行系统调用的类型:

  • 同步,G被阻塞,M也被阻塞,那么就要从P上调度下来,但是该G仍依附于原来的M(G的执行是在M上),之后一个新的M会被调用到原来的P上去执行其他G。一旦系统调用完之前的G加入到P的私有队列LRQ中,原来的G被“雪藏”(休眠),之后等时机调用 image.png
  • 异步,G未阻塞,M也未阻塞(如多路复用),G的请求被netpoll网络轮询接手,G不再依附M,依附于netpoll,等待结束再返回LRQ image.png

    异步情况,是将I/O任务变成CPU任务

2.GMP数据结构

GO调度器三个核心基础数据结构(这也验证了前文的说法,协程就是一个特殊的数据结构),下面都是1.20版本

2.1 G struct

  1. G结构体,这个是简化版
type g struct {
    stack       stack   // offset known to runtime/cgo
    stackguard0 uintptr // offset known to liblink
    stackguard1 uintptr // offset known to liblink

    _panic    *_panic // innermost panic - offset known to liblink
    _defer    *_defer // innermost defer
    m         *m      // current m; offset known to arm liblink
    sched     gobuf
    timer        *timer
    gopc           uintptr
  • stack,看到stack我们就明白了,协程应该和线程一样有自己的栈,这里初始化的栈空间在2K左右,表示一个用户代码的执行流,即用户栈,线程拥有的栈叫做系统栈,stack很简单,栈顶和栈底
type stack struct {
    lo uintptr
    hi uintptr
}

typedef unsigned long long int  uint64;
typedef uint64          uintptr;
  • stackguard0和stackgruad1都是描述栈的,问题来了G是可以有千万个的,内存会不会很快用光?初始内存设为2k就是不让栈拥有太大的内存空间,这个内存也会根据G的调用链动态增加的
  • _panic和_defer 是等待被执行的函数链表,defer很熟悉了吧
  • m,G在M上执行,当然要绑定一个m
  • timer ,定时器
  • gopc是创建该g的指令地址
  • sched等是G运行时的现场数据
type gobuf struct {
    sp   uintptr
    pc   uintptr
    g    guintptr
    ctxt unsafe.Pointer
    ret  uintptr
    lr   uintptr
    bp   uintptr // for framepointer-enabled architectures
}

G的运行不光需要栈,还需要SP和PC等寄存器

当g被调离CPU时,调度器会把CPU寄存器的值保存在g的gobuf类型对象成员中
被调度运行时,把gobuf保存的变量恢复到寄存器中

G的状态 image.png

2.2 M struct

M结构体,也省略了很多

type m struct {
        g0      *g     // goroutine with scheduling stack
        
        curg          *g       // current running goroutine
        p             puintptr // attached p for executing go code (nil if not executing go code)
        nextp         puintptr
        oldp          puintptr // the p that was attached before executing a syscall
        id            int64
  • curg表示正在运行的G
  • p表示m绑定的p,m和p的关系是1:1
  • g0 是一个特殊的G

m的状态有两种


image.png
  • 自旋:找G去工作,M会从与他绑定的P获取G,也会从netpoll获取G(异步系统调用),还会从其他P中偷取G
  • 非自旋:没工作睡大觉,之后会进入休眠

2.3 特殊的g0

我们在前一章学习了调度器的第二个核心思想,要限制同时运行的线程数N,N为CPU核数(当前进程可获取的核数),m执行G,那么m如何调度G?这个调度功能委托给g0,它是在创建每一个线程M时,都会自动创建的第一个协程G,代号为0 image.png

并且调度不是它唯一的工作,它有着固定且更大的栈空间

  • G的创建,使用关键词go 创建协程实际是委托给g0创建 image.png

    我们看到新的G放在队列头部,优先级更高,这也解释了为什么创建协程有机会触发调度

  • defer函数分配
  • 垃圾回收
  • 栈增长

运筹帷幄,总览全局,GMP的G明明应该是g0的g呀

2.4 P结构体

P

type p struct {
    m           muintptr   // back-link to associated m (nil if idle)

    // Queue of runnable goroutines. Accessed without lock.
    runqhead uint32
    runqtail uint32
    runq     [256]guintptr
    schedtick   uint32 
P的状态 image.png
  • Pgcstop:程序刚初始化时,都为该状态,初始化完成后变为Pidle,发生GC也会变成该状态
  • Pidle
  • Pruning: M需要运行时,绑定的P为该状态
  • psyscall:G调用系统调用阻塞,设置P状态,

`#参考
1.GMP是什么
2.GO scheduler
3.程序员面试笔记
4.特殊的g0

相关文章

  • go 调度器实现

    GO 语言的调度器 目录 GMP 模型简介 调度器实现机制 GMP 模型简介 先来一张经典的GMP 关系图 G 是...

  • Go - GMP模型

    简述 G — 表示 Goroutine,它是一个待执行的任务; M — 表示操作系统的线程,它由操作系统的调度器调...

  • Go GMP

    一文彻底弄懂go中的调度GMP先说,协程的本质是用户态的线程,用户对其有控制权限,内存占用少,切换代价低。 再来解...

  • 【Go 精选】从 GM 到 GMP 模型

    本文简单介绍了 GM 和 GMP 模型,其中分析了 GMP 针对 GM 存在问题的优化点。 1 GM 模型 GM ...

  • gmp模型

    1、G G是Goroutine的缩写,相当于操作系统中的进程控制块,在这里就是Goroutine的控制结构,是对G...

  • 欧盟GMP

    读欧盟GMP-1 说实话,国内2010版GMP就是在欧盟GMP的基础上修订的,连框架都很像。。 包括: 001 质...

  • [视频版]-Golang深入理解GMP

    HELLO GOPHER! 相信越来越多的Go浪小伙伴,都对Golang的GMP调度器流连忘返,GMP很多书籍都有...

  • 文件管理分析

    读《欧盟GMP》-3 全球的GMP,与我们息息相关的是中国GMP2010版,欧盟GMP,美国CGMP。 今天看看文...

  • go并发的那些事

    思考:go为什么那么擅长并发? 答:从设计理解上来讲我觉着golang的CSP并发模型与GMP调度器是基石。你看虽...

  • go 的并发调度(一) GMP 模型

    协程和线程的历史关系? 抢占式和协同式抢占式就是线程无法决定自己执行多久,由操作系统(或其他分配系统)来分配一个线...

网友评论

      本文标题:GO基础学习(9)GMP模型-GMP

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