zoukankan      html  css  js  c++  java
  • GMP模型简介

    G:表示goroutine,每个goroutine都有自己的栈空间,定时器,初始化的栈空间在2k左右,空间会随需求在增长。

    M:抽象化代表内核线程,记录内核线程栈信息,当goroutine调度到线程是,使用该协程自己的栈信息。

    P:代表调度器,负责调度协程,维护一个本地协程队列,M从P获得协程并执行,同时还负责部分内存的管理。

    图片

    1.全局队列:存放等待运行的G。

    2.P的本地队列:同全局队列类似,存放的也是等待运行的G,存的数量有限,不超过256个。新建G的时候,G优先加入到P的本地队列,如果队列满了,则会把本地队列一半的G移动到全局队列。

    3.P列表:所有的P都在程序启动时创建,并保存在数组中,最多有GOMAXPROCS(可配置)个。

    4.M:线程想运行任务就需要获得P,从P的本地队列获取G,P队列为空时,M会尝试从全局队列拿一批G放到P的本地队列,或从其他P的本地队列偷一半放到自己P的本地队列。M运行G,G执行之后,M会从P获取下一个G,不断重复。

    协程调度器和OS调度器通过M结合起来,每个M都代表了一个内核线程,OS调度器负责把内核线程分配到CPU的核上执行。

    P的数量:由启动时环境变量GOMAXPROCS或者runtime方法GOMAXPROCS()决定。意味着在程序执行的任意时刻都只有GOMAXPROCS个协程同时运行。

    M的数量:go程序启动时,会设置M的最大数量,默认10000.但内核很难支持这么多的线程数,所以这限制可以忽略。一个M阻塞了,会创建新的M。

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

    P创建:在确定了P的最大数量n后,运行时系统就会根据这个数量创建n个P。

    M创建:没有足够的M来关联P并运行其中可运行的G。比如所有的M此时都阻塞了,而P中还有很多就绪任务,就会去寻找空闲的M,而没有空闲的,就会去创建新的M 。

    调度器的设计策略

    复用线程:避免频繁的创建、销毁线程,而是对线程的复用。

    1)work stealing机制

    ​ 当本线程无可运行的G时,尝试从其他线程绑定的P偷取G,而不是销毁线程。

    2)hand off机制

    ​ 当本线程因为G进行系统调用阻塞时,线程释放绑定的P,把P转移给其他空闲的线程执行。

    利用并行GOMAXPROCS设置P的数量,最多有GOMAXPROCS个线程分布在多个CPU上同时运行。GOMAXPROCS也限制了并发的程度,比如GOMAXPROCS = 核数/2,则最多利用了一半的CPU核进行并行。

    抢占:在coroutine中要等待一个协程主动让出CPU才执行下一个协程,在Go中,一个goroutine最多占用CPU 10ms,防止其他goroutine被饿死,这就是goroutine不同于coroutine的一个地方。

    全局G队列:在新的调度器中依然有全局G队列,但功能已经被弱化了,当M执行work stealing从其他P偷不到G时,它可以从全局G队列获取G。

    图片

    从上图我们可以分析出几个结论:

    1、我们通过 go func()来创建一个goroutine;

    2、有两个存储G的队列,一个是局部调度器P的本地队列、一个是全局G队列。新创建的G会先保存在P的本地队列中,如果P的本地队列已经满了就会保存在全局的队列中;

    3、G只能运行在M中,一个M必须持有一个P,M与P是1:1的关系。M会从P的本地队列弹出一个可执行状态的G来执行,如果P的本地队列为空,就会想其他的MP组合偷取一个可执行的G来执行;

    4、一个M调度G执行的过程是一个循环机制;

    5、当M执行某一个G时候如果发生了syscall或则其余阻塞操作,M会阻塞,如果当前有一些G在执行,runtime会把这个线程M从P中摘除(detach),然后再创建一个新的操作系统的线程(如果有空闲的线程可用就复用空闲线程)来服务于这个P;

    6、当M系统调用结束时候,这个M会尝试获取一个空闲的P执行,并把可运行的G放入到这个P的本地队列。如果获取不到P,那么这个线程M变成休眠状态, 加入到空闲线程中,然后这个G会被放入全局队列中。

    调度器的生命周期

    图片

    GMP优点:

    G的数量远远大于M的数量,可以说Go程序可以利用少量的内核级线程来支撑大量协程的并发M:N模型。多个协程通过用户级别的上下文切换来共享内核线程M的计算资源,但对于操作系统来说并没有线程上下文切换产生的性能损耗。

    为了更充分利用线程的计算资源,Go调度器采用了以下几种调度策略:

    任务窃取
    为了提高 Go 并行处理能力,调高整体处理效率,当每个 P 之间的 G 任务不均衡时,调度器允许从 GRQ(全局运行队列),或者其他 P 的 LRQ (本地队列)中获取 G 执行。

    减少阻塞

    1.由于原子、互斥量或channel操作调用导致阻塞,调度器将把当前阻塞的 Goroutine 切换出去,重新调度 LRQ 上的其他 Goroutine

    简单将Goroutine归纳为协程并不合适,因为它运行时会创建多个线程来执行并发任务,且任务单元可被调度到其它线程执行。这更像是多线程和协程的结合体,能最大限度提升执行效率,发挥多核处理器能力。

    参考博文:
    https://mp.weixin.qq.com/s/rfjysi-LB-uFiGiZjh-XNw

    https://studygolang.com/articles/27934

    https://www.jianshu.com/p/abe79d86ff27

  • 相关阅读:
    ACM ICPC 2008–2009 NEERC MSC A, B, C, G, L
    POJ 1088 滑雪 DP
    UVA 11584 最短回文串划分 DP
    POJ 2531 Network Saboteur DFS+剪枝
    UVa 10739 String to Palindrome 字符串dp
    UVa 11151 Longest Palindrome 字符串dp
    UVa 10154 Weights and Measures dp 降维
    UVa 10271 Chopsticks dp
    UVa 10617 Again Palindrome 字符串dp
    UVa 10651 Pebble Solitaire 状态压缩 dp
  • 原文地址:https://www.cnblogs.com/hzpeng/p/15516121.html
Copyright © 2011-2022 走看看