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()
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调度器的更多相关文章
- Go语言goroutine调度器概述(11)
本文是<go调度器源代码情景分析>系列的第11篇,也是第二章的第1小节. goroutine简介 goroutine是Go语言实现的用户态线程,主要用来解决操作系统线程太“重”的问题,所谓 ...
- Golang/Go goroutine调度器原理/实现【原】
Go语言在2016年再次拿下TIBOE年度编程语言称号,这充分证明了Go语言这几年在全世界范围内的受欢迎程度.如果要对世界范围内的gopher发起一次“你究竟喜欢Go的哪一点”的调查,我相信很多Gop ...
- Go语言goroutine调度器初始化(12)
本文是<Go语言调度器源代码情景分析>系列的第12篇,也是第二章的第2小节. 本章将以下面这个简单的Hello World程序为例,通过跟踪其从启动到退出这一完整的运行流程来分析Go语言调 ...
- golang GMP goroutine调度器
Goroutine可以动态的伸缩栈的大小,最小2-4kb,最大1GB
- go语言调度器源代码情景分析之五:汇编指令
本文是<go调度器源代码情景分析>系列 第一章 预备知识的第4小节. 汇编语言是每位后端程序员都应该掌握的一门语言,因为学会了汇编语言,不管是对我们调试程序还是研究与理解计算机底层的一些运 ...
- go语言调度器源代码情景分析之二:CPU寄存器
本文是<go调度器源代码情景分析>系列 第一章 预备知识的第1小节. 寄存器是CPU内部的存储单元,用于存放从内存读取而来的数据(包括指令)和CPU运算的中间结果,之所以要使用寄存器来临时 ...
- go语言调度器源代码情景分析之一:开篇语
专题简介 本专题以精心设计的情景为线索,结合go语言最新1.12版源代码深入细致的分析了goroutine调度器实现原理. 适宜读者 go语言开发人员 对线程调度器工作原理感兴趣的工程师 对计算机底层 ...
- Go调度器介绍和容易忽视的问题
本文记录了本人对Golang调度器的理解和跟踪调度器的方法,特别是一个容易忽略的goroutine执行顺序问题,看了很多篇Golang调度器的文章都没提到这个点,分享出来一起学习,欢迎交流指正. 什么 ...
- Golang调度器GMP原理与调度全分析(转 侵 删)
该文章主要详细具体的介绍Goroutine调度器过程及原理,包括如下几个章节. 第一章 Golang调度器的由来 第二章 Goroutine调度器的GMP模型及设计思想 第三章 Goroutine调度 ...
随机推荐
- Flask 基础总结回顾
1.Flask Request # from flask import request request.form # 获取FormData中的数据 request.args # 获取URL中的数据 r ...
- maven设定项目编码
今天在DOS下执行mvn compile命令时报错说缺少必要符号,事实上根本就没有缺少,但何以如此呢,为啥eclipse在编译时就没有这问题呢? 原因是编码的问题造成的! eclipse在编译的使用使 ...
- RSA加密&解密【Java&Scala】
一.简介 RSA加密算法是一种非对称加密算法.在公开密钥加密和电子商业中RSA被广泛使用. RSA公开密钥密码体制.所谓公开密钥密码体制就是使用不同的加密密钥与解密密钥,是一种“由已知加密密钥推导出解 ...
- 【书评:Oracle查询优化改写】第四章
[书评:Oracle查询优化改写]第四章 BLOG文档结构图 一.1 导读 各位技术爱好者,看完本文后,你可以掌握如下的技能,也可以学到一些其它你所不知道的知识,~O(∩_∩)O~: ① check的 ...
- MySQL修炼之路五
1. 存储引擎和锁 1. 存储引擎(处理表的处理器) 1. 基本操作 1. 查看所有存储引擎 mysql>show engines; 2. 查看已有表的存储引擎 mysql>show cr ...
- 常用docker管理UI
1. HumpBacks 特性 Web UI Supporting, Easy to use. Container Grouping and Isolation. Container Upgrades ...
- Python的logging模块详解
Python的logging模块详解 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.日志级别 日志级别指的是产生的日志的事件的严重程度. 设置一个级别后,严重程度 ...
- Layui外部js修改表格内容
//测试修改数据的方法! var _tds=$(".layui-table-body.layui-table-main:eq(1) tr:eq(1)").children(); _ ...
- spring boot项目打包成jar后请求访问乱码解决
在启动jar的时候添加一个配置 -Dfile.encoding=utf-8 java -Dfile.encoding=utf-8 -jar xxxxtest-0.1.jar
- 运维笔试Python编程题
一.用Python语言把列表[1,3,5,7,9]倒序并将元素变为字符类型,请写出多种方法: 第一种方法: list = [1, 3, 5, 7, 9] list.reverse() list2 = ...