zoukankan      html  css  js  c++  java
  • Goroutine调度器

    前言

    并发(并行)一致都是编程语言的核心主题,不同于其他语言,例如C/C++语言用户序自行借助pthread创建线程,Golang天然就给出了并发解决方案:goroutine。

    Goroutine

    写过Golang程序的朋友都知道,go func就可以启动一个goroutine,但是goroutine究竟是什么呢?goroutine是一个用户态的线程,或者说是逻辑线程,或者说是golang实现的协程。但是操作系统并不知道goroutine,goroutine的调度由golang runtime负责,对应的操作系统执行体线程也是由runtime负责创建。这就涉及goroutine的G-P-M调度模型。

    G-P-M调度模型

    Golang能够拥有强大的并发能力需要归功于G-P-M调度模型,首先需要解释G、P、M分别代表什么:

    • G 代表Goroutine,每个Goroutine对应一个G结构体,G存储Goroutine的运行栈、状态以及任务信息,可重用。Goroutine栈采用按需动态分配的方式,初始化大小为2KB,最大为1GB(64位机器)。
    • P 代表Processor,逻辑处理器。P维护Goroutine各种队列,mcache和状态。P的数量决定了最大可并行的Goroutine数量(前提:系统物理CPU核数>=P数量)。P的数量可以通过GOMAXPROCS设置。但是不能超过256,超过256会设置为256。
    • M 代表内核级别线程,一个M就是一个线程。默认最大限制为10000个。

    调度逻辑

    从图中可以看出,一共有两个物理线程M,每个M都绑定一个处理器P,每个P维护一个就绪状态的Goroutine队列,灰色的表示在等待P调度,蓝色的G代表正绑定P在M中执行。当程序中出现go func时,会将func挂载在灰色的等待队列中。当执行的Goroutine(G0)调度阻塞的系统调度时,P会切到另外的M'中,如果没有可用的M'就会创建一个,继续执行队列中的G。待系统调用返回时M0会重新绑定可用的P,如果没有可用的P就会把G0放到Global队列中,然后自己进入休眠。所有的P会周期性的检查Global队列,并且执行其中的G。如下图所示:

    work-stealing算法

    当P分配的G任务很快就执行完成时,P会先看Global runqueue还有G可以执行,如果没有就会到其他P的local runqueue中偷G。一般都会偷走一半,确保操作系统的每个M都能得到充分利用。例如下图中的第二个P没有G可以执行,所以把第一个P的Gm和G偷过来执行。

    抢占式调度

    按照上面的已经介绍过的理论,假如我将GOMAXPROC设为1,表示只有一个P,同时运行A,B两个Goroutine,其中A,B都是死循环,那岂不是有一个Goroutine永远都没有办法得到调度?

    示例代码如下:

    package main
    
    import (
        "fmt"
        "runtime"
    )
    
    
    func main() {
        runtime.GOMAXPROCS(1)
        go func(){
            for {
                fmt.Print("A")
            }
        }()
        for {
            fmt.Print("B")
        }
    }

    但是实验的结果大概是先输出A 10ms然后再输出B 10ms,如此交替。说明并没有Goroutine由于其他的Goroutine“贪婪”而“饥饿”。这需要归功于Golang runtime的后台监控线程sysmon,这是一个特殊的m,不需要绑定p可以执行。每隔10ms运行一次,将运行时间太久的G发出抢占式调度的请求。一旦G的抢占位设置为true,那么这个G下次调用函数或者方法时,runtime便可以将G抢占,并将其移出运行态。

    channel阻塞或network IO情况下的调度

    如果G被阻塞在某个channel或者网络IO操作上时,G会被放到某个wait队列中,而P会尝试调度下一个runnable的G。等channel 或者网络IO操作完成后,在wait队列中的G会被唤醒,标记为runnable重新排队执行。

    总结

    文章介绍了Golang自带的goroutine调度器G-P-M调度模型,G-P-M调度算法最大限度的发挥了并发性能,同时在一些异常情况下也能正常快速调度。

    参考

    http://morsmachine.dk/go-scheduler

    https://tonybai.com/2017/06/23/an-intro-about-goroutine-scheduler/

  • 相关阅读:
    java每日一学--数据校验20131008
    转载:正则表达式30分钟入门[1]
    【Java可移植性】编程规范每日一学--20130923
    【Java可移植性】编程规范每日一学--20130917
    【Java资源管理】编程规范每日一学--20130916
    瀑布流第二种方式————基于ajax方式
    瀑布流方式一
    JSONP跨域
    利用iframe和form上传和预览图片
    Ajax全套
  • 原文地址:https://www.cnblogs.com/makelu/p/11129161.html
Copyright © 2011-2022 走看看