前言

并发(并行)一致都是编程语言的核心主题,不同于其他语言,例如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()
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/

Goroutine调度器的更多相关文章

  1. Go语言goroutine调度器概述(11)

    本文是<go调度器源代码情景分析>系列的第11篇,也是第二章的第1小节. goroutine简介 goroutine是Go语言实现的用户态线程,主要用来解决操作系统线程太“重”的问题,所谓 ...

  2. Golang/Go goroutine调度器原理/实现【原】

    Go语言在2016年再次拿下TIBOE年度编程语言称号,这充分证明了Go语言这几年在全世界范围内的受欢迎程度.如果要对世界范围内的gopher发起一次“你究竟喜欢Go的哪一点”的调查,我相信很多Gop ...

  3. Go语言goroutine调度器初始化(12)

    本文是<Go语言调度器源代码情景分析>系列的第12篇,也是第二章的第2小节. 本章将以下面这个简单的Hello World程序为例,通过跟踪其从启动到退出这一完整的运行流程来分析Go语言调 ...

  4. golang GMP goroutine调度器

    Goroutine可以动态的伸缩栈的大小,最小2-4kb,最大1GB

  5. go语言调度器源代码情景分析之五:汇编指令

    本文是<go调度器源代码情景分析>系列 第一章 预备知识的第4小节. 汇编语言是每位后端程序员都应该掌握的一门语言,因为学会了汇编语言,不管是对我们调试程序还是研究与理解计算机底层的一些运 ...

  6. go语言调度器源代码情景分析之二:CPU寄存器

    本文是<go调度器源代码情景分析>系列 第一章 预备知识的第1小节. 寄存器是CPU内部的存储单元,用于存放从内存读取而来的数据(包括指令)和CPU运算的中间结果,之所以要使用寄存器来临时 ...

  7. go语言调度器源代码情景分析之一:开篇语

    专题简介 本专题以精心设计的情景为线索,结合go语言最新1.12版源代码深入细致的分析了goroutine调度器实现原理. 适宜读者 go语言开发人员 对线程调度器工作原理感兴趣的工程师 对计算机底层 ...

  8. Go调度器介绍和容易忽视的问题

    本文记录了本人对Golang调度器的理解和跟踪调度器的方法,特别是一个容易忽略的goroutine执行顺序问题,看了很多篇Golang调度器的文章都没提到这个点,分享出来一起学习,欢迎交流指正. 什么 ...

  9. Golang调度器GMP原理与调度全分析(转 侵 删)

    该文章主要详细具体的介绍Goroutine调度器过程及原理,包括如下几个章节. 第一章 Golang调度器的由来 第二章 Goroutine调度器的GMP模型及设计思想 第三章 Goroutine调度 ...

随机推荐

  1. 83.基于Vue SEO的四种方案(小结)

    前言:众所周知,Vue SPA单页面应用对SEO不友好,当然也有相应的解决方案,下面列出几种最近研究和使用过的SEO方案,SRR和静态化基于Nuxt来说. 1.SSR服务器渲染:2.静态化:3.预渲染 ...

  2. jQuery-使页面回到顶部

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  3. Linu如何查看磁盘占用情况及处理办法

    free -h: 查看当前剩余的内存大小 df: 查看文件系统磁盘使用率,可能free -h得到的剩余空间还有很多,但是df查询得到的部分文件系统磁盘使用率较高 当发现磁盘使用率较高的时候,可以: 先 ...

  4. docker 安装 oracle

    docker 安装 oracle 部署链接:https://www.linuxidc.com/Linux/2017-03/141760.htm 本次使用docker安装oracle 1.搜索可使用的镜 ...

  5. Python入门篇-递归函数Recursion

    Python入门篇-递归函数(recursion) 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.递归概述 (1)函数直接或者间接调用自身就是递归: (2)递归需要有边界,递归 ...

  6. 瑞克和莫蒂|S3E03 Rick为什么要把自己变成一根腌黄瓜?

    给尚未入坑Rick and Morty的小伙伴们为一点背景知识    S3E1在说什么? rick从监狱里回来,顺手搞垮了统治地球的银河联邦政府.Jerry因此丢了工作,非常不高兴.于是向Beth质问 ...

  7. windows下面,PHP如何启动一些扩展功能

    我今天在试这个时,发现php有些默认设置,是需要人为介入修改的. 比如,当我们在安装一个软件,而这个软件需要启用php一些扩展功能. 那么,按一般套路,将php.ini文件里的相关行的注释去掉即可. ...

  8. Lovers(HDU6562+线段树+2018年吉林站)

    题目链接 传送门 题意 初始时有\(n\)个空串,然后进行\(q\)次操作,操作分为以下两种: wrap l r x:把\(l,r\)中的每个字符串的首尾都加入\(x\),如\(s_i=121,x=3 ...

  9. JAVA的概念理解:JavaSE、JavaEE、JavaME、jdk、jre、ide

    JavaSE是Java Standard Edtion的缩写,译成中文就是Java标准版,也是Java的核心.无论是 JavaEE(Java企业版)还是JavaME(Java微型版)都是以JavaSE ...

  10. P4160 [SCOI2009]生日快乐[dfs]

    题目描述 windy的生日到了,为了庆祝生日,他的朋友们帮他买了一个边长分别为 X 和 Y 的矩形蛋糕. 现在包括windy,一共有 N 个人来分这块大蛋糕,要求每个人必须获得相同面积的蛋糕. win ...