Erlang调度器详解(转)
来源:互联网 发布:js在线测试 编辑:程序博客网 时间:2024/05/12 18:16
Erlang调度器详解
Erlang之所以是软实时系统,是因为有一些重要的隐含特征。其中之一是我在我的上一篇文章,Erlang Garbage Collection Details and Why It Matters中提到的垃圾回收机制,而另一个值得一提的就是调度器机制。在本文中我将讲解它的历史、现状,以及用于控制与监测的API。
什么是调度
抢占式:抢占式调度器有权力中断任务并在稍后使它们继续进行,且无需被中断任务的协作。抢占式调度需要考虑考虑任务的优先级、时间片和归约数(reduction)。
- 协作式:协作式调度器在进行上下文切换时需要任务的协作。这种调度器只需要等待执行中的任务结束或自行释放控制权,然后再开始下一个任务。
那么对于必须对请求在特定时间内进行响应的实时系统,哪一种调度机制更适合呢?协作式调度不能满足实时系统的需求,因为在协作式调度机制下,任务有可能永远都不会返回。所以实时系统通常使用抢占式调度。
Erlang中的调度
作为一个实时多任务平台,Erlang采用抢占式调度。Erlang调度器的职责是选择一个Erlang进程并执行它。同时它也负责垃圾回收和内存管理。对进程的选择基于各个进程独立可调整的优先级,对同一优先级的进程使用轮询(round-robin fashion)调度策略。另一方面,调度器还需要根据归约数(reduction)来中断运行中的进程。归约数(reduction)是一个通常会随着每次函数调用增长的计数器,当它达到最大值时,调度器便会中断运行中的进程并进行上下文切换。例如,在Erlang/OTP R12B中,该最大值默认为2000。
Erlang的任务调度机制已经有久远的历史,它也在随着时间不断地改进。这些改进与Erlang针对SMP(对称多处理结构)的变化有关。
R11B以前的调度
在R11B之前,Erlang并不支持SMP,所以只存在一个运行在操作系统主进程上的调度器,相应的,也只有一个运行队列(Run Queue)。调度器从运行队列中选出Erlang进程或IO任务并进行执行。
Erlang VM+--------------------------------------------------------+| || +-----------------+ +-----------------+ || | | | | || | Scheduler +--------------> Task # 1 | || | | | | || +-----------------+ | Task # 2 | || | | || | Task # 3 | || | | || | Task # 4 | || | | || | Task # N | || | | || +-----------------+ || | | || | Run Queue | || | | || +-----------------+ || |+--------------------------------------------------------+
这种方式无需对数据结构加锁,应用也无法享受并发带来的好处。
R11B及R12B的调度
由于Erlang虚拟机对SMP的支持,在每一个操作系统的线程中都可以运行一个调度器,调度器的总数为1到1024个。不过,所有的调度器都从同一个运行队列中获取任务。
Erlang VM+--------------------------------------------------------+| || +-----------------+ +-----------------+ || | | | | || | Scheduler # 1 +--------------> Task # 1 | || | | +---------> | || +-----------------+ | +----> Task # 2 | || | | | | || +-----------------+ | | | Task # 3 | || | | | | | | || | Scheduler # 2 +----+ | | Task # 4 | || | | | | | || +-----------------+ | | Task # N | || | | | || +-----------------+ | +-----------------+ || | | | | | || | Scheduler # N +---------+ | Run Queue | || | | | | || +-----------------+ +-----------------+ || |+--------------------------------------------------------+
由于存在并行部分,所有共享的数据结构都需要加锁保护。例如,运行队列本身作为一个共享的数据结构,就必须受到保护。尽管锁会降低性能,但新调度器在多核系统上的性能提升还是非常让人感兴趣。
该调度器已知的性能瓶颈有以下几点:
- 随着调度器数量的增加,共享的运行队列会成为瓶颈。
增加了ETS和Mnesia中锁的复杂性。
- 当多个进程向一个进程发送消息时,增加了发生锁冲突的可能。
一个等待获取锁的进程将会阻塞它的调度器。
不过,在下个版本引入了调度器独立的运行队列后,这些瓶颈得到了解决。
R13B以后的调度
在该版本以后,每一个调度器都拥有自己的运行队列。这减少了在多核系统中运行大量调度器时造成的锁冲突,同时还提高了总体性能。 Erlang VM+--------------------------------------------------------+| || +-----------------+-----------------+ || | | | || | Scheduler # 1 | Run Queue # 1 <--+ || | | | | || +-----------------+-----------------+ | || | || +-----------------+-----------------+ | || | | | | || | Scheduler # 2 | Run Queue # 2 <----> Migration || | | | | Logic || +-----------------+-----------------+ | || | || +-----------------+-----------------+ | || | | | | || | Scheduler # N | Run Queue # N <--+ || | | | || +-----------------+-----------------+ || |+--------------------------------------------------------+
虽然目前的方式解决了锁冲突的问题,但同时又带来了如下的担忧:
- 多个运行队列之间的任务分配是否足够公平?
- 若出现一个调度器超负荷运转而其他调度器相对空闲的情况,应如何解决?
- 空闲调度器应遵循什么规则,从忙的调度器处窃取任务?
- 若任务的数量相对于调度器的数量过少,应如何解决?
用于控制与监测的API
调度器线程
$ erl +S MaxAvailableSchedulers:OnlineSchedulers
最大调度器数只能在启动时设定,而可用调度器数还可以在运行时进行调整。例如,我们启动模拟器时令最大调度器数为16,可用调度器数为8。$ erl +S 16:8
随后在终端中,我们可以以下面的方式改变可用调度器数。> 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标志可以以百分比形式设定以上参数。进程优先级
PID = spawn(fun() -> process_flag(priority, high), %% ... end).
优先级可以是基元 low | normal | high | max 中的一个,优先级默认为normal,而max
是为Erlang运行时系统保留的,原则上不应在其他情况下被使用。运行队列的统计数据
%% 一切就绪> erlang:statistics(online_schedulers). %% => 4> erlang:statistics(run_queue). %% => 0%% 同时创建10个计算进程> [spawn(fun() -> calc:prime_numbers(10000000) end) || _ <- lists:seq(1, 10)].%% 运行队列中尚有未完成的进程> erlang:statistics(run_queue). %% => 8%% 终端没有被阻塞,太好了!> calc:prime_numbers(10). %% => [2, 3, 5, 7]%% 稍等一会> erlang:statistics(run_queue). %% => 4%% 稍等一会> erlang:statistics(run_queue). %% => 0
由于同时运行的进程数要多于当前可用调度器数,调度器要执行完所有进程并清空运行队列是需要一些时间的。有趣的是,正是由于抢占式的调度策略,在创建这些了这些任务繁重的进程之后,Erlang终端并没有被阻塞。调度器没有放任某些进程耗尽其他重要进程的CPU时间,要实现实时系统,这一点是非常必要的特性。总结
资料来源
- Official Documentation for erl script
- Official Documentation for erlang module
- How Erlang Does Scheduling
- Inside the Erlang VM
- Erlang Scheduler: What Does It Do
- Erlang调度器详解(转)
- erlang 脏调度器支持
- erlang 调度
- Erlang的调度原理
- erlang是怎么调度的
- MySQL事件调度器详解
- Quartz任务调度器详解
- Yarn 调度器Scheduler详解
- MySQL事件调度器详解
- Yarn调度器Scheduler详解
- ExecutorService(任务调度器)详解
- ORACLE调度(schedule)详解(转)
- [转]Golang中goroutine的调度器详解
- Erlang/OTP 17.0-rc1 新引入的"脏调度器"浅析
- Erlang/OTP 17.0-rc1 新引入的"脏调度器"浅析
- Erlang调度器的一些细节以及它重要的原因(译文)
- Erlang并发机制 –进程调度
- Erlang的调度原理(译文)
- Android Activity 学习记录
- AndroidStudio2.2.3 JNI与NDK开发之一:生成可调用.so库
- 贝叶斯分类的原理及流程
- uCOS-III移植到STM32分析
- JSP基础
- Erlang调度器详解(转)
- 鼠标移动特效代码
- hdu 2522 1/n 循环节
- js读取解析JSON类型数据【申明:来源于网络】
- Java开发中的23种设计模式详解
- 748 A. Santa Claus and a Place in a Class codeforces
- ComponentName使用
- linux设备驱动模型 学习
- JSP使用JavaBean