深入理解Go语言(03):scheduler调度器 - 基本介绍
一:什么是调度
平常我们在生活中会有哪些调度的例子呢?比如十字路口的红绿灯,它就是一种调度系统。在交通十字路口,每个路口上多多少少有一些车辆,为了限制这些车辆不随意行驶,就建起了红绿灯调度系统。红绿灯可以有序的调度这些车辆行驶,使这些车辆快速的通过路口。
那为什么需要红绿灯来进行调度呢?
1:使车辆有序的行驶不至于相撞
2:使车辆能快速的通过路口
上面的红绿灯系统有哪些元素呢?1. 红绿灯系统-负责调度车辆 2. 车辆 3. 十字路口
由此我们可以看出一个调度系统基本元素有2个:1. 调度系统 2. 被调度的东西 3. 应用的资源
因为资源(十字路口)是有限的,每个方向只有一条路可供车辆行驶,而车辆在每个路口又是很多的,这是不是就形成了资源和使用者之间的矛盾了,为了解决这个矛盾,红绿灯调度系统应运而生。
如果我有多层路口,比如高架桥,多了一层,那走上面的车辆是不是就不需要等红绿灯了。这应该是调度系统在生活中的启示。
所以我们可以说调度系统是协调被调度的物件合理的使用资源。
也可以回顾下以前学习Linux系统中的调度,被调度的物件是线程或者进程,资源是cpu,为了协调有限的cpu资源在多个线程(进程)中合理公平使用,就有了调度器。
那golang中的调度呢?
跟上面的红绿灯调度有一些相似点。
golang的调度是为了多个协程能合理的利用线程。这里的协程(goroutine)相当于车辆了,线程相当于十字路口(也许不恰当)。
go在运行时,会运行很多协程goroutine,也就是我们常说的并发,go为了能使这些协程有序的快速的在线程上执行,就需要进行调度了。
还有,go为了方便的控制goroutine,比如把当前线程中的goroutine移交到其他线程继续执行,从而避免应线程阻塞影响协程的运行。
还有,go中引入了GC,在执行GC的时候,要求所有goroutine停止,自己实现调度器,就可以方便实现这个功能了。
相对于操作系统的线程来说,goroutine更加的轻量,占用的内存更小,上下文进行切换时换入换出的数据也更少。
二:goroutine简介
我们说Go是语言级别的并发语言。为什么是语言级别?那不还有系统级别,对,操作系统级别就实现了多线程,多进程这种并发。
有2个支持高并发的模型:CSP 和 Actor(erlang)。Go 选择了CSP,Go为了提供更容易的并发使用方法,提供了2个重要的概念 goroutine
和channel
。
goroutine来自协程的概念,让一组可复用的函数运行在一组线程之上,即使有协程阻塞,该线程的其他协程也可以被runtime
调度,转移到其他可运行的线程上。最关键的是,程序员看不到这些底层的细节,这就降低了编程的难度,提供了更容易的并发。
goroutine非常的轻量,只占几KB,并且这几KB足够goroutine运行完。所以我们运行成千上万的goroutine成为可能,因为不会占用太多的内存资源。
channel为goroutine之间提供了通信功能。
不要通过共享内存来通信,而应该通过通信来共享内存
三:go调度模型GMP
go的调度模型,一开始并不是GPM模型,而是经过了一个发展过程。
老的调度模型只有G和M,没有P。为什么只有GM呢?因为这是一个简单的模型,最开始开发时一定容易实现。
M:代表OS线程,它是运行goroutine的
G:就是goroutine
它还有一个重要的数据结构:全局队列global runqueue。
为什么会有队列?它有什么作用?
全局队列是用来存放goroutine(G)的。启动那么多goroutine,总要一个地方把G存起来以便M来调用。
多个M会从这个全局队列里获取G来进行运行。
GM模型如下图:
M要执行G,或者把G放回去,都要访问全局队列,而且M还是多个,所以必须对全局队列加锁保证互斥。
这必然导致多个M对锁的竞争。这也是老调度器的一个缺点。
其实老调度器有4个缺点:详见Scalable Go Scheduler Design Doc
- 创建、销毁、调度G都需要每个M获取锁,这就形成了激烈的锁竞争
- M转移G会造成延迟和额外的系统开销。
- M中的mcache是用来存放小对象的,mcache和栈都和M关联造成了大量的内存开销和差的局部性
- 系统调用导致频繁的线程阻塞和取消阻塞操作增加了系统开销。
所以Go语言在2012年重新设计了调度器方案(Scalable Go Scheduler Design Doc,设计方案)。
在GO1.1中新调度器引入了:
- P(processor),它包含了运行goroutine的资源。如果线程M想运行G(goroutine),比如先获取P,P中还包含了可运行的G队列。
- work stealing:当M绑定的P没有可运行的G时,它可以从其他运行的M那里偷取G来运行。这个的作用就是避免M因为没有可运行的G时产生饥饿的问题。
work stealing算法地址
新调度器就是一个GPM模型了:
- G:goroutine,用户级别的线程(协程)。我们在程序里用go关键字创建的一个协程。
- P:processor,相当于一个处理器,它包含了goroutine运行的资源,M必须和一个P关联才能运行G。P还包含自己的本地队列(local runqueue)来保存G。为什么要搞一个本地队列?这样就可以避免竞争锁了。
- M:工作线程,代表机器(machine)。这个线程是OS来处理的,OS负责把线程放到cpu上去运行。
当然新调度器也有一个很重要的数据结构:全局运行队列 global runqueue。
题外话:引入P,解决了老调度器的一些问题,但是同时也增加了调度器的复杂度。这个是必然的,引入新东西必然会增加系统的复杂度。就看能不能很好的解决问题,权衡利弊。
GPM模型如下图:
(图来自:https://www.ardanlabs.com/blog/2018/08/scheduling-in-go-part2.html)
GRQ:全局队列 global runqueue
LRQ:P的本地队列 local runqueue
G:goroutine协程
M:OS的线程,用来绑定P,运行G,真正执行指令的人。
P:processor,goroutine运行时所需要的资源。P的数量可以用runtime.GOMAXPROCS()来控制。
每个P会分配一个LRQ(本地队列)去处理P的上下文要执行的Goroutines 。这些Goroutines会在绑定到P的M上进行上下文的切换。GRQ(全局队列)会处理还没有分配到P上的Goroutines 。
新的调度器中有全局队列,但功能已经被弱化了,当M执行work stealing从其他P偷不到G时,它可以从全局队列获取G。
抢占式调度
G-P-M模型的实现算是Go scheduler的一大进步,但Scheduler还有一个很头疼的问题,那就是不支持抢占式调度。
为什么要抢占式调度?
因为一旦某个G出现死循环或者永久循环的代码逻辑,那么G将永久占用分配给他的P和M,位于同一个P中的其他G将得不到调度,出现饿死的情况。还有一种情况,当只有一个P(GOMAXPROCS=1),
整个Go程序中的其他G都将饿死。于是Dmitry Vyukov提出了《Go Preemptive Scheduler Design》
并在Go 1.2中实现了“抢占式”调度
一些说明:
1、M绑定P,才可以不断的去运行G,如果M没有可运行的G,也可以抢占式调度(依靠sysmon)
2、从上图可以看出,每个P都有自己的本地队列,也有一个全局队列
3、M,P,G三者的数量,M默认10000,可以设置,通过SetMaxThreds修改,P默认是CPU的核数,可以设置,通过GOMAXPROCS修改,
G没有数量限制,可以创建成百上万个,甚至百万。
4、M可以与P解绑,也可以休眠
新的调度器有没有缺点?有
1、 runqueue只是一个没有优先级的队列,所以会按照先进先出的顺序来运行Goroutine。
2、调度goroutine时公平性没有很好的保证:已经提议进行修改。
3、 runqueue没有利用缓存,使用缓存[栈而不是队列]可以加速Goroutine的访问。
四:参考:
- https://juejin.im/post/5ce11a39f265da1baf7cbc61 理解golang调度之二 :Go调度器
- https://www.youtube.com/watch?v=YHRO5WQGh0k GopherCon 2018: Kavya Joshi - The Scheduler Saga
- https://qcrao.com/2019/09/02/dive-into-go-scheduler/ 深度解密Go语言之scheduler
- https://www.ardanlabs.com/blog/2018/08/scheduling-in-go-part2.html
- https://tonybai.com/2017/06/23/an-intro-about-goroutine-scheduler/
深入理解Go语言(03):scheduler调度器 - 基本介绍的更多相关文章
- quartz2.3.0(十二)通过RMI协议向Scheduler调度器远程添加job任务
此代码示例通过RMI协议向Scheduler调度器远程添加job任务. 代码文件包括:job任务类(SimpleJob.java).RMI服务端server类(RemoteServerExample. ...
- scrapy 源码解析 (四):启动流程源码分析(四) Scheduler调度器
Scheduler调度器 对ExecutionEngine执行引擎篇出现的Scheduler进行展开.Scheduler用于控制Request对象的存储和获取,并提供了过滤重复Request的功能. ...
- IO调度器原理介绍
IO调度器(IO Scheduler)是操作系统用来决定块设备上IO操作提交顺序的方法.存在的目的有两个,一是提高IO吞吐量,二是降低IO响应时间.然而IO吞吐量和IO响应时间往往是矛盾的,为了尽量平 ...
- Go语言的GPM调度器是什么?
我是平也,这有一个专注Gopher技术成长的开源项目「go home」 导读 相信很多人都听说过Go语言天然支持高并发,原因是内部有协程(goroutine)加持,可以在一个进程中启动成千上万个协程. ...
- Yarn 调度器Scheduler详解
理想情况下,我们应用对Yarn资源的请求应该立刻得到满足,但现实情况资源往往是有限的,特别是在一个很繁忙的集群,一个应用资源的请求经常需要等待一段时间才能的到相应的资源.在Yarn中,负责给应用分配资 ...
- Yarn 组件的指挥部 – 调度器Scheduler
linux基础 为hadoop集群的搭建扫清了障碍,也为内存的管理,文件系统的管理扫清了障碍 接着到Hadoop的阶段,首先做集群的安装,深入到使用这两个核心的组件,分布式文件系统HDFS,解决大量数 ...
- hadoop之 Yarn 调度器Scheduler详解
概述 集群资源是非常有限的,在多用户.多任务环境下,需要有一个协调者,来保证在有限资源或业务约束下有序调度任务,YARN资源调度器就是这个协调者. YARN调度器有多种实现,自带的调度器为Capaci ...
- 调度器简介,以及Linux的调度策略
进程是操作系统虚拟出来的概念,用来组织计算机中的任务.但随着进程被赋予越来越多的任务,进程好像有了真实的生命,它从诞生就随着CPU时间执行,直到最终消失.不过,进程的生命都得到了操作系统内核的关照.就 ...
- 调度器简介,以及Linux的调度策略(转)
进程是操作系统虚拟出来的概念,用来组织计算机中的任务.但随着进程被赋予越来越多的任务,进程好像有了真实的生命,它从诞生就随着CPU时间执行,直到最终消失.不过,进程的生命都得到了操作系统内核的关照.就 ...
- Linux内核——进程管理之CFS调度器(基于版本4.x)
<奔跑吧linux内核>3.2笔记,不足之处还望大家批评指正 建议阅读博文https://www.cnblogs.com/openix/p/3262217.html理解linux cfs调 ...
随机推荐
- Jmeter学习之五_跟踪被测试服务器的performance
Jmeter学习之五_跟踪被测试服务器的performance 背景 这几天简单学习了一些基本的测试过程. 可以实现一些简单基本的功能了. 今天晚上继续进行了jmeter的一些学习. 想着可以在测试人 ...
- [转帖]使用 Shell 运算进行进制转换 16进制转10进制
使用 Shell 运算进行进制转换 工作时候常常遇到一些问题,拿到的数字是16进制的,但是运算的时候是10进制的,shell可以很方便的处理这类的进制转换问题,一种情况是使用 Shell 运算把一个数 ...
- [转帖]CPU的制造和概念
https://plantegg.github.io/2021/06/01/CPU%E7%9A%84%E5%88%B6%E9%80%A0%E5%92%8C%E6%A6%82%E5%BF%B5/ 为了让 ...
- 在WPF应用中,结合阿里矢量图标库使用Geometry图标
在我们的SqlSugar开发框架的WPF应端中,有时候我们需要在按钮或者其他界面元素上使用一些图标,框架中我们可以使用 lepoco/wpfui 项目的图标库,也可以使用Font-Awesome-WP ...
- Redis启用认证
要在Redis中启用认证,您需要在Redis配置文件中设置requirepass指令.以下是步骤: 找到Redis配置文件.这通常是redis.conf,可能位于/etc/redis/或/etc/目录 ...
- TienChin 引入 MyBatisPlus
在父工程当中添加版本号,统一管理: <mybatis-plus.version>3.5.1</mybatis-plus.version> 在父工程当中添加 MyBatisPlu ...
- 我手写了一个RPC框架。成功帮助读者斩获字节、阿里等大厂offer。
本着开源精神,本项目README已经同步了英文版本.另外,项目的源代码的注释大部分也修改为了英文. 如访问速度不佳,可放在 Gitee 地址:https://gitee.com/SnailClimb/ ...
- LyScript 自实现汇编搜索功能
通过对LyScript自动化插件进行二次封装,实现从内存中读入目标进程解码后的机器码,并通过Python代码在这些机器码中寻找特定的十六进制字符数组,或直接检索是否存在连续的反汇编指令片段等功能. 插 ...
- (转)时代的见证:集成更新的Windows 7旗舰版、专业版镜像
制作缘起:微软曾于2019年提供过两份内部集成更新的英文旗舰版.专业版镜像(参见:集成IE11+最新补丁:微软新版Windows 7镜像泄露),方便用户安装,缩短更新过程.经我们下载安装研究发现,这两 ...
- 【图论】【Matlab】最小生成树之Kruskal算法【贪心思想超详细详解Kruskal算法并应用】
最小生成树之Kruskal算法 注意:内容学习来自:b站CleverFrank数模算法精讲 导航 前言 实际问题引入 Kruskal算法 整体代码展示 尾声 前言 博主今天给大家带来的是最小生成树中两 ...