Erlang 使用 scheduler 来对 Erlang process 进行调度。
为了可以利用多核资源,在启动的时候,会根据 CPU 的核心数或物理线程数创建 schduler,每个 schduler 都在一个线程中运行,而每一个线程被绑定到了对应的 CPU core。
Erlang runtime 会根据负载的情况,动态计算所需要的 scheduler 的数量,如果负载过高,则增加 scheduler 数量,如负载过低,则减少 scheduler 的数量。
如果某个 scheduler 负载过高,也会把这个 scheduler 的 process 迁移(migrate)给别的 scheduler。
如果一个 scheduler 没有 process 可以执行,就会从其他 scheduler 里偷 process。
利用多核资源还有其他方案,在 Erlang 没有支持 SMP 之前(1998 年由一篇硕士论文提出,2006 官方正式发布),可以通过启动多个 Erlang VM 来达到类似的效果,但显然没有多线程、共享内存灵活和高效。
再比如 Rust 的 tokio,则是使用线程池来执行 callback,同样可以利用多核,同时还解决了公平调度的问题,但这样就需要锁来避免多个线程同时操作同一个变量, 好在 Rust 解决起这个问题并不难。
下面主要是介绍,在 check_balance 的时候,scheduler 的数量是如何计算的。
Erlang VM 没执行一定的 reductions 的时候,就会触发 check_balance 方法,并且只有一个 scheduler 会触发这个方法,具体可以看上一篇文章。
如果不需要动态计算 scheduler 的话,可以通过设置 +scl true 这个参数,来关掉这个公能。毕竟有些场景节能不是考虑的问题。
首先 active 必须要落在一个区间内,active 最小不能小于 max(1, half_full_scheds),最大不能大于 blnc_no_rqs。
half_full_scheds 是指,在一半的时间里,没有进入等待状态(ERTS_RUNQ_FLG_OUT_OF_WORK)的 scheduler 的数量。
blnc_no_rqs 通过 get_no_runqs 获得,是指这一阶段内,使用过的 scheduler 数量。
之后,如果上一次活跃的数量,小于当前活跃的数量,则将其设置为当前活跃的数量
如果不满足这个条件,则
full_scheds 是指在这个阶段内,从来没有进入等待状态的 scheduler 的数量。
既在这个阶段内,有 scheduler 没有进入等待状态,则将 scheduler 的数量设置为所有 scheduler 执行的 reductions 数量除以触发 balance_check 需要的 reductions 的数量。
否则将 scheduler 设置为 上一次的活跃数减 1。
最后,还会根据负载情况,做一次修正。
prev_rise 是上一次,scheduler 增加的时候的数量,设置方法是:
if (balance_info.last_active_runqs < current_active)
ERTS_BLNCE_SAVE_RISE(current_active, mmax_len, scheds_reds);
也就是说,如果负载减少的不是很多的话,scheduler 的数量会保持之前的数量不变。
if (!forced && half_full_scheds != blnc_no_rqs) {
int min = 1;
if (min < half_full_scheds)
min = half_full_scheds;
if (full_scheds) {
active = (scheds_reds - 1)/ERTS_RUNQ_CHECK_BALANCE_REDS_PER_SCHED+1;
}
else {
active = balance_info.last_active_runqs - 1;
}
if (balance_info.last_active_runqs < current_active) {
ERTS_BLNCE_SAVE_RISE(current_active, mmax_len, scheds_reds);
active = current_active;
}
else if (active < balance_info.prev_rise.active_runqs) {
if (ERTS_PERCENT(mmax_len,
balance_info.prev_rise.max_len) >= 90
&& ERTS_PERCENT(scheds_reds,
balance_info.prev_rise.reds) >= 90) {
active = balance_info.prev_rise.active_runqs;
.active_runqs;
}
}
if (active < min)
active = min;
else if (active > blnc_no_rqs)
active = blnc_no_rqs;
if (active == blnc_no_rqs)
goto all_active;
}