Erlang调度器细节探析
Erlang调度器细节探析
Erlang的很多基础特性使得它成为一个软实时的平台。其中包括垃圾回收机制,详细内容可以参见我的上一篇文章Erlang Garbage Collection Details and Why It Matters
什么是调度
一般来说,调度是一种将工作分配给工作者的机制。这些工作可以是数学运算,字符串处理,数据提取,工作者指的是类似于Green Threads或者原生线程等这种资源。调度器就是执行调度任务的程序,它在某种程度上提供:最大化吞吐,公平执行,最小化响应时间和最小化延时。调度是多任务操作系统/虚拟机的主要部分。它分为两种:
抢占式:抢占式调度器在所有运行任务中切换上下文,并且有权利抢占(中断)任务执行并稍后恢复执行而不需要被强占的任务配合。它基于优先级,时间切片,reduction技术。
协作式:协作式调度器在进行上下文切换时需要任务的配合。在这种调度模式下调度器让运行的任务周期性主动释放控制权或者在idle状态时主动释放,然后开始执行新任务,等待新任务自发返回控制权。
现在的问题是哪种调度方式适合必须在限定时间内响应的实时系统。协作式调度不能满足要求,因为实时系统中的运行任务可能永远不会在限定时间内主动释放控制权或者返回。所以实时系统通常使用抢占式调度。
Erlang调度
Erlang作为实时多任务平台,它使用抢占式调度。Erlang调度器的职责是选择一个Process然后执行它的代码。它也负责垃圾回收和内存管理。如何选择process取决于它们的优先级,每个process的优先级都是可配置的。对于每个优先级,多个process轮询调度。另一方面,抢占一个process取决于它最后一次执行到目前的确定数目的reductions操作,而不管优先级。reductions是每个线程的计数器,如果有函数调用就增加计数。当该计数器到达max reduction
,调度器就会抢占process并切换上下文。在Erlang/OTP R12B
中,max reduction
是2000
。
Erlang调度机制有很长的历史,历经数次改变。这些改变也受Erlang中对称多线程(SMP)特性的影响。
Erlang R11B之前的调度
在R11B
版本之前,Erlang不支持SMP,只有一个调度器运行在OS进程中的线程,也只有一个Run Queue。调度器从run queue中选择可运行的process或I/O任务执行。
Erlang 虚拟机
+--------------------------------------------------------+
| |
| +-----------------+ +-----------------+ |
| | | | | |
| | Scheduler +--------------> Task # 1 | |
| | | | | |
| +-----------------+ | Task # 2 | |
| | | |
| | Task # 3 | |
| | | |
| | Task # 4 | |
| | | |
| | Task # N | |
| | | |
| +-----------------+ |
| | | |
| | Run Queue | |
| | | |
| +-----------------+ |
| |
+--------------------------------------------------------+
这种实现不需要锁数据结构但是老旧代码不能享受新处理器并行快餐。
Erlang R11B/R12B 的调度
在这两个版本中由于SMP的加入,OS进程的一个线程可以运行1-1024个调度器。然而,这个版本的调度器从公共run queue选择可运行任务而不像之前那样只有一个run queue
Erlang 虚拟机
+--------------------------------------------------------+
| |
| +-----------------+ +-----------------+ |
| | | | | |
| | Scheduler # 1 +--------------> Task # 1 | |
| | | +---------> | |
| +-----------------+ | +----> Task # 2 | |
| | | | | |
| +-----------------+ | | | Task # 3 | |
| | | | | | | |
| | Scheduler # 2 +----+ | | Task # 4 | |
| | | | | | |
| +-----------------+ | | Task # N | |
| | | | |
| +-----------------+ | +-----------------+ |
| | | | | | |
| | Scheduler # N +---------+ | Run Queue | |
| | | | | |
| +-----------------+ +-----------------+ |
| |
+--------------------------------------------------------+
由于并行的加入,所有的共享数据结构都被锁保护。run queue它自身是一个共享数据结构,必须锁住。虽然锁会造成性能惩罚(performance penalty),但是在多核处理器上运行性能有所提升。
这个版本有一些已知的性能瓶颈:
- 当调度器数目增加时公共run queue会成为一个瓶颈
- 对涉及锁的ETS tables操作会影响Mnesia
- 当很多process向一个process发送消息会增加锁冲突几率
- process等待锁会阻塞它的调度器
然而,在下个版本可以看到,为每个调度器创建一个run queue解决了上述问题。
Erlang R13B 的调度
在这个版本中每个调度器有一个run queue。它大大降低了多核系统上锁冲突的几率,也提高了总体的性能
Erlang虚拟机
+--------------------------------------------------------+
| |
| +-----------------+-----------------+ |
| | | | |
| | Scheduler # 1 | Run Queue # 1 <--+ |
| | | | | |
| +-----------------+-----------------+ | |
| | |
| +-----------------+-----------------+ | |
| | | | | |
| | Scheduler # 2 | Run Queue # 2 <----> Migration |
| | | | | Logic |
| +-----------------+-----------------+ | |
| | |
| +-----------------+-----------------+ | |
| | | | | |
| | Scheduler # N | Run Queue # N <--+ |
| | | | |
| +-----------------+-----------------+ |
| |
+--------------------------------------------------------+
现在访问run queue导致锁冲突的几率大大降低,但也引入了新议题:
- run queue的任务划分对于process来说公平吗
- 如果一个process超负荷另一个idle怎么办
- 调度器应该基于什么顺序来将超负荷的任务转移
- 如果我们运行很多调度器但是只有少量任务怎么办
这些人们关心的议题使得Erlang开发团队引入新概念使得调度公平高效,即Migration Logic。它基于之前搜集的统计信息来控制run queue任务数,使其保持相对平衡。
然而,我们不应该依赖于调度控制run queue,因为很可能后续版本会有所改变。
控制和监控API
这里是一些Erlang模拟器的flag,它也可以控制/监控虚拟机内部调度行为。
- 调度线程
在启动erlang模拟器时,可以通过flag传递两个由冒号(:)分离的数字来指定
$ erl +S MaxAvailableSchedulers:OnlineSchedulers
最大可用调度线程数只能在启动时指定,但online调度线程数既可以在启动时指定也可以在运行时改变。
比如,我们可以启动16个可用调度线程,8个online调度线程。
$ erl +S 16:8
然后像下面一样调用函数改变online线程数目
> erlang:system_info(schedulers). %% => returns 16
> erlang:system_info(schedulers_online). %% => returns 8
> erlang:system_flag(schedulers_online, 16). %% => returns 8
> erlang:system_info(schedulers_online). %% => returns 16
另外,使用+SP
flag可以按百分比设置。
- process优先级
正如我之前说的,调度器选择process执行取决于优先级,这个优先级可以由erlang:process_flag/2
指定
PID = spawn(fun() ->
process_flag(priority, high),
%% ...
end).
优先级可以是low | normal | high | max
之一。默认优先级是normal
。max
为erlang运行时保留,用户不应该使用它。
- run queue信息统计
之前说到run queue存放可以执行的process,等待调度器选择。现在可以调用erlang:statistics(run_queue)
获取run queue中所有可以执行的process的数目。举个实际的例子,我们启动erlang模拟器,指定4个online调度线程,分配10个CPU密集的process并发执行,任务可以考虑计算素数个数。
%% Everything is clean and ready
> erlang:statistics(online_schedulers). %% => 4
> erlang:statistics(run_queue). %% => 0
%% Spawn 10 heavy number crunching processes concurrently
> [spawn(fun() -> calc:prime_numbers(10000000) end) || _ <- lists:seq(1, 10)].
%% Run queues have remaining tasks to do
> erlang:statistics(run_queue). %% => 8
%% Erlang is still responsive, great!
> calc:prime_numbers(10). %% => [2, 3, 5, 7]
%% Wait a moment
> erlang:statistics(run_queue). %% => 4
%% Wait a moment
> erlang:statistics(run_queue). %% => 0
因为并发process比online调度线程多,调度器会花上较多时间执行所有process直到run queue为空。有趣的是在spawn这些CPU密集process后,由于抢占式调度,erlang模拟器一直保持响应。它不会让这些流氓process消耗所有运行时,而让其它可能轻量级但很重要的process饿死,这对于实时系统来说是非常棒的一个特性。
总结
虽然实现一个抢占式调度系统很复杂,但万幸这不是开发者的事,它内置于erlang虚拟机。另一方面,对于一个所有process资源需要相对公平,响应时间不能太长的实时系统来说,额外的跟踪,平衡,选择,抢占线程的成本是完全可以接受的。还有,完全抢占调度需要操作系统的支持,但就平台或者库的角度上,Erlang虚拟机可以说是最独特的那个:JVM线程依赖于操作系统调度器,CAF,一个基于actor模型的C++库,使用协作式调度。Golang不是完全抢占式,Python的Twisted也不是,Ruby的event machine和nodejs同样也不是。这不是说erlang总是最好的选择,只是对于要求低延时的实时平台Erlang是一个好的选择
其他
- 原文Erlang Scheduler Details and Why It Matters@Hamidreza Soleimani
- Process是指erlang的轻量级进程,不是os process,需要注意下
- online sheculde thread的online翻译成_在线_感觉不好,就直接保留了
Erlang调度器细节探析的更多相关文章
- Erlang调度器
1. Erlang 抢占式调度 Erlang实现公平调度基于Reduction Budget(运行次数限制).每一个进程创建时初始reduction budget值为2000,任何Erlang系统中的 ...
- 朴素的UNIX之-调度器细节
0.多进程调度的本质 我们都知道UNIX上有一个著名的nice调用.何谓nice,当然是"好"了.常规的想法是nice值越大越好,实际上,nice值越好,自己的优先级越低.那么为何 ...
- Erlang 进程被抢占的条件——一个进程长时霸占调度器的极端示例
最近研究 binary 的实现和各种操作对应的 beam 虚拟机汇编指令,发现有一些指令序列是不可重入的,比如说有的指令构造一个上下文(也就是某种全局状态),然后下一条指令会对这个上下文做操作(具体的 ...
- Erlang/OTP 17.0-rc1 新引入的"脏调度器"浅析
最近在做一些和 NIF 有关的事情,看到 OTP 团队发布的 17 rc1 引入了一个新的特性“脏调度器”,为的是解决 NIF 运行时间过长耗死调度器的问题.本文首先简单介绍脏调度器机制的用法,然后简 ...
- 开源中文分词工具探析(五):FNLP
FNLP是由Fudan NLP实验室的邱锡鹏老师开源的一套Java写就的中文NLP工具包,提供诸如分词.词性标注.文本分类.依存句法分析等功能. [开源中文分词工具探析]系列: 中文分词工具探析(一) ...
- go语言调度器源代码情景分析之一:开篇语
专题简介 本专题以精心设计的情景为线索,结合go语言最新1.12版源代码深入细致的分析了goroutine调度器实现原理. 适宜读者 go语言开发人员 对线程调度器工作原理感兴趣的工程师 对计算机底层 ...
- Linux调度器 - 用户空间接口
一.前言 Linux调度器神秘而充满诱惑,每个Linux工程师都想深入其内部一探究竟.不过中国有一句古话叫做“相由心生”,一个模块精巧的内部逻辑(也就是所谓的“心”)其外延就是简洁而优雅的接口(我称之 ...
- MapReduce多用户任务调度器——容量调度器(Capacity Scheduler)原理和源码研究
前言:为了研究需要,将Capacity Scheduler和Fair Scheduler的原理和代码进行学习,用两篇文章作为记录.如有理解错误之处,欢迎批评指正. 容量调度器(Capacity Sch ...
- 开源中文分词工具探析(三):Ansj
Ansj是由孙健(ansjsun)开源的一个中文分词器,为ICTLAS的Java版本,也采用了Bigram + HMM分词模型(可参考我之前写的文章):在Bigram分词的基础上,识别未登录词,以提高 ...
随机推荐
- JavaScript练习2
今天做了一些JS数组的练习题 一.往数组中插入一个数字 var attr = [1,2,3,4,5,6]; var c = 7; for(var i=0;i<attr.length;i++) { ...
- 一步一步创建ASP.NET MVC5程序[Repository+Autofac+Automapper+SqlSugar](三)
前言 上一篇<一步一步创建ASP.NET MVC5程序[Repository+Autofac+Automapper+SqlSugar](二)>我们通过如下操作: 创建实体及工具类 创建Re ...
- 学习笔记-使用cmd命令行创建nodejs项目
使用cmd命令行创建nodejs项目 1)在DOS下cd 进入到你想要创建项目的路径. 2)mkdir sing (创建一个sing文件夹) 3)cd sing 4) npm init (初始化工程 ...
- Redis 学习开发笔记
Redis特点: 1.速度快 2.支持丰富的数据类型:字符串.哈希列表.集合 3.操作具有原子性,所有Redis操作都是原子操作 4.多实用工具,可应用如缓存,消息队列,应用程序中任何短期数据,如we ...
- CTF---Web入门第二题 上传绕过
上传绕过分值:10 来源: Justatest 难度:易 参与人数:5847人 Get Flag:2272人 答题人数:2345人 解题通过率:97% bypass the upload 格式:fla ...
- 深入设计电子计算器(一)——CPU框架及指令集设计
版权申明:本文为博主窗户(Colin Cai)原创,欢迎转帖.如要转贴,必须注明原文网址 http://www.cnblogs.com/Colin-Cai/p/8278418.html 作者:窗户 Q ...
- python 元组学习
元组用tuple表示,用,分割开,和列表类似,但是没有排序修改等高级操作.简单地说就是终态的...... >>> tuple1 = (1,2,3)>>> type( ...
- 关于responseBody注解中文乱码的问题解决
在web.xml中定义字符集过滤器: <filter> <filter-name>SpringEncodingFilter</filter-name> <fi ...
- 启动tomcat时,一直卡在Deploying web application directory这块的解决方案
本来今天正常往服务器上扔一个tomcat 部署一个项目的, 最后再启动tomcat 的时候 发现项目一直都访问不了,看了一下日志: [root@iz8vbdzx7y7owm488t4d89z bin] ...
- ZendOptimizer怎么安装?Php网站打开显示乱码
http://jingyan.baidu.com/article/4e5b3e1952a99291901e24cf.html 安装zendoptimizer软件 1 网上下载对应的zend版本,点击进 ...