zoukankan      html  css  js  c++  java
  • Go语言GMP模型

    进程、线程、协程

    • 进程:进程是系统进行资源分配的基本单位,有独立的内存空间,单切换代价极高,进程间通信也比较麻烦
    • 线程:线程是CPU调度和分派的基本单位,线程依附于进程,与其他线程共享进程的资源,仅有自己的(程序计数器,一组寄存器的值,和栈),线程切换代价小(但是线程之间的切换可能会设计用户态和内核态的切换),由于共享进程资源,所以线程之间通信比较方便。
    • 协程:协程是一种用户态的轻量级线程,协程的调度完全由用户控制,协程切换只需要保存和恢复任务的上下文,没有内核的开销。协程间通信也比较简单(协程间本身是不可抢占的,由于操作系统的调度机制无法影响到它,因此一般存在用户自定义的调度机制)(也可以这么说内核线程依然叫“线程(thread)”,用户线程叫“协程(co-routine)".)
      线程的代价
    • 使用过C++/Java的同学应该都知道在做服务端开发的时候,对于多线程的使用,繁琐程度,以及对于多线程情况下异常情况的分析,是何等头疼,如果线上流量过大,对于这种多线程的操作更是得慎重,当然对于机器资源的消耗也是随之增加的,其实对于程序员来说最主要就是简单,高效~
    1. 空间大小:一般情况下一个thread的创建大小默认占用栈空间1M,而goroutine默认则是2kb
    2. 切换开销:thread的切换是需要通过用户态达到内核态,会设计到上下文的切换开销,对应资源的耗费会很大
    3. 线程通信:常见的线程通信有三种方式(C++),使用起来相对比较复杂,最棘手的问题就是共享内存,一般会使用锁,使用到锁,就会引申各种锁的问题,比如死锁。
    4. 资源回收:创建线程相对简单,回收线程资源会麻烦很多,常见有detached和join俩种方式,对于无法大量创建线程的时候,会考虑多路复用方式,这样的麻烦就是另个层次了。

    GMP模型

    • G:Goroutine,每个Gotoutine对应一个G结构体,G存储Goroutine的运行堆栈,状态,以及任务函数,可重用(函数实体)G需要保存到P才能被调度执行
    • M:machine,os内核线程抽象,代表真正执行计算的资源,在绑定有效的P后,进入schedule循环;而shcedule循环的机制大致是从Global队列,P的local队列以及wait队列中获取。
      M的数量是不固定的,有Go Runtime调整,为了防止创建过多OS线程导致系统调度不过来,目前默认设置为10000个,M不保存G的上下文,这是G可以跨M的基础。
    • P:Processor,表示逻辑处理器,对G来说,P相当于CPU核,G只有绑定到P才能被调度。对M来说,P提供了相关的执行环境,入内存分配状态,任务队列等。
      P 的数量决定了系统内最大可并行的 G 的数量(前提:物理 CPU 核数 >= P 的数量)。
      P 的数量由用户设置的 GoMAXPROCS 决定,但是不论 GoMAXPROCS 设置为多大,P 的数量最大为 256。
    • 调度器循环的机制大致是从各种队列、P 的本地队列中获取 G,切换到 G 的执行栈上并执行 G 的函数,调用 Goexit 做清理工作并回到 M,如此反复。
      可以通过经典的地鼠推车搬砖的模型来说明其三者关系:
    • 地鼠(Gopher)的工作任务是:工地上有若干砖头,地鼠借助小车把砖头运送到火种上去烧制。M 就可以看作图中的地鼠,P 就是小车,G 就是小车里装的砖。

    Processor(P):
    根据用户设置的 GoMAXPROCS 值来创建一批小车(P)。
    Goroutine(G):
    通过 Go 关键字就是用来创建一个 Goroutine,也就相当于制造一块砖(G),然后将这块砖(G)放入当前这辆小车(P)中。
    Machine (M):
    地鼠(M)不能通过外部创建出来,只能砖(G)太多了,地鼠(M)又太少了,实在忙不过来,刚好还有空闲的小车(P)没有使用,那就从别处再借些地鼠(M)过来直到把小车(P)用完为止。
    这里有一个地鼠(M)不够用,从别处借地鼠(M)的过程,这个过程就是创建一个内核线程(M)。
    需要注意的是:地鼠(M) 如果没有小车(P)是没办法运砖的,小车(P)的数量决定了能够干活的地鼠(M)数量,在 Go 程序里面对应的是活动线程数;

    调度过程

    • 首先创建一个G对象,G对象保存到P本地队列或者是全局队列。P此时去唤醒一个M。P继续执行它的执行序。M寻找是否有空闲的P,如果有则将该G对象移动到它本身。接下来M执行一个调度循环(调用G对象->执行->清理线程→继续找新的Goroutine执行)。
    • M执行过程中,随时会发生上下文切换。当发生上线文切换时,需要对执行现场进行保护,以便下次被调度执行时进行现场恢复。Go调度器M的栈保存在G对象上,只需要将M所需要的寄存器(SP、PC等)保存到G对象上就可以实现现场保护。当这些寄存器数据被保护起来,就随时可以做上下文切换了,在中断之前把现场保存起来。如果此时G任务还没有执行完,M可以将任务重新丢到P的任务队列,等待下一次被调度执行。当再次被调度执行时,M通过访问G的vdsoSP、vdsoPC寄存器进行现场恢复(从上次中断位置继续执行)。
    参考
    https://www.it610.com/article/1293176199840866304.htm
    https://studygolang.com/articles/27934?fr=sidebar

  • 相关阅读:
    win10 UWP button
    内网分享资源
    内网分享资源
    CF724F Uniformly Branched Trees
    win10 UWP FlipView
    win10 UWP FlipView
    win10 UWP FlipView
    搭建阿里云 centos mysql tomcat jdk
    搭建阿里云 centos mysql tomcat jdk
    win10 UWP 申请微软开发者
  • 原文地址:https://www.cnblogs.com/frankltf/p/13825246.html
Copyright © 2011-2022 走看看