赞
踩
Erlang之所以是软实时系统,是因为有一些重要的隐含特征。其中之一是我在我的上一篇文章,Erlang Garbage Collection Details and Why It Matters中提到的垃圾回收机制,而另一个值得一提的就是调度器机制。在本文中我将讲解它的历史、现状,以及用于控制与监测的API。
抢占式:抢占式调度器有权力中断任务并在稍后使它们继续进行,且无需被中断任务的协作。抢占式调度需要考虑考虑任务的优先级、时间片和归约数(reduction)。
作为一个实时多任务平台,Erlang采用抢占式调度。Erlang调度器的职责是选择一个Erlang进程并执行它。同时它也负责垃圾回收和内存管理。对进程的选择基于各个进程独立可调整的优先级,对同一优先级的进程使用轮询(round-robin fashion)调度策略。另一方面,调度器还需要根据归约数(reduction)来中断运行中的进程。归约数(reduction)是一个通常会随着每次函数调用增长的计数器,当它达到最大值时,调度器便会中断运行中的进程并进行上下文切换。例如,在Erlang/OTP R12B中,该最大值默认为2000。
Erlang的任务调度机制已经有久远的历史,它也在随着时间不断地改进。这些改进与Erlang针对SMP(对称多处理结构)的变化有关。
在R11B之前,Erlang并不支持SMP,所以只存在一个运行在操作系统主进程上的调度器,相应的,也只有一个运行队列(Run Queue)。调度器从运行队列中选出Erlang进程或IO任务并进行执行。
- Erlang VM
-
- +--------------------------------------------------------+
- | |
- | +-----------------+ +-----------------+ |
- | | | | | |
- | | Scheduler +--------------> Task # 1 | |
- | | | | | |
- | +-----------------+ | Task # 2 | |
- | | | |
- | | Task # 3 | |
- | | | |
- | | Task # 4 | |
- | | | |
- | | Task # N | |
- | | | |
- | +-----------------+ |
- | | | |
- | | Run Queue | |
- | | | |
- | +-----------------+ |
- | |
- +--------------------------------------------------------+

这种方式无需对数据结构加锁,应用也无法享受并发带来的好处。
由于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中锁的复杂性。
一个等待获取锁的进程将会阻塞它的调度器。
不过,在下个版本引入了调度器独立的运行队列后,这些瓶颈得到了解决。
- Erlang VM
-
- +--------------------------------------------------------+
- | |
- | +-----------------+-----------------+ |
- | | | | |
- | | Scheduler # 1 | Run Queue # 1 <--+ |
- | | | | | |
- | +-----------------+-----------------+ | |
- | | |
- | +-----------------+-----------------+ | |
- | | | | | |
- | | Scheduler # 2 | Run Queue # 2 <----> Migration |
- | | | | | Logic |
- | +-----------------+-----------------+ | |
- | | |
- | +-----------------+-----------------+ | |
- | | | | | |
- | | Scheduler # N | Run Queue # N <--+ |
- | | | | |
- | +-----------------+-----------------+ |
- | |
- +--------------------------------------------------------+

虽然目前的方式解决了锁冲突的问题,但同时又带来了如下的担忧:
这些担忧促使Erlang开发团队引入了一个新的概念以使得调度更加的高效和公平,迁移逻辑(Migration Logic)。迁移逻辑利用从系统中收集的统计数据,控制和平衡了运行队列。
用于控制与监测的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时间,要实现实时系统,这一点是非常必要的特性。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。