如何确定流程的"经理"和"工作线程"线程的优先级(或为其设置调度策略)?



我正在运行一个进程(在基于Linux 3.x的操作系统上(,其中:

  • 少数线程是"管理器"线程(为了简单起见,假设它们决定哪些工作线程应该做什么,但不做任何I/O,并且它们所需的CPU时间总共比工作线程短/短得多(
  • 更多的线程是"工作者"线程:它们在计算方面做繁重的工作,我对它们在任何时候被抢占都没有问题

可能存在超额订阅(即,使用HT的英特尔处理器上的工作线程数是内核数的两倍多(。现在,我看到的是"管理器"线程没有足够频繁地获得处理器时间。他们并没有完全"饿死",我只是想给他们打气。所以,我自然会考虑设置不同的线程优先级(我在Linux上(,但后来我注意到线程调度器的不同选择及其效果。在这一点上,我感到困惑,或者更确切地说,我不清楚:

  • 我应该为经理选择哪种调度策略,为工人选择哪种
  • 我应该将线程优先级设置为什么(如果有的话(
  • 我需要偶尔让我的线程yield((吗

注意:

  • 我有意不对语言或线程池机制做任何说明。我想在更普遍的背景下提出这个问题
  • 请不要对CPU核心进行假设。他们可能有很多,也可能只有一个,也许我需要每个核心的员工(或员工和经理(
  • 工作线程可以执行I/O,也可以不执行I/O。不过,对于他们不执行任何I/O的情况,欢迎回答
  • 除了运行我的应用程序之外,我真的不需要系统的响应能力很强。我的意思是,我宁愿能够在那里进行SSH,并让我的输入在没有显著延迟的情况下得到响应,但没有真正的限制

UPD 12.02.2015:我做了一些实验。

理论

有一个明显的解决方案可以将"管理器"线程调度程序更改为RT(提供SCHED_DEADLINE/SCHED_FIFO策略的实时调度程序(。在这种情况下,"管理器"线程总是比系统中的大多数线程具有更大的优先级,因此它们几乎总是在需要CPU时获得CPU

但是,还有另一种解决方案可以让您继续使用CFS调度器。您对"工作者"线程用途的描述类似于批处理调度(在古代计算机很大的时候,用户必须将他的工作放入队列,等待数小时才能完成(。Linux CFS通过SCHED_batch策略支持批处理作业,通过SCHED_NORMAL策略支持对话框作业。

内核代码(kernel/sched/fair.c(中也有有用的注释:

/*
 * Batch and idle tasks do not preempt non-idle tasks (their preemption
 * is driven by the tick):
 */
if (unlikely(p->policy != SCHED_NORMAL) || !sched_feat(WAKEUP_PREEMPTION))
    return;

所以,当"管理器"线程或其他事件唤醒"工作者"时,后者只有在系统中有空闲CPU的情况下才能获得CPU,或者当"管理者"耗尽其时间片时(调整它会改变任务的权重(。

如果不更改调度程序策略,您的问题似乎无法解决。如果"工作者"线程非常繁忙,而"管理者"很少醒来,那么他们将获得相同的vruntime奖金,因此"工作者"将始终优先于"管理器"线程(但您可能会增加它们的权重,因此它们会更快地耗尽奖金(。

实验

我有一台带有2个Intel Xeon E5-2420 CPU的服务器,它为我们提供了24个硬件线程。为了模拟两个线程池,我使用了自己的TSLoad工作负载生成器(并在运行实验时修复了几个错误:(。

有两个线程池:tp_manager有4个线程,tp_worker有30个线程,都运行busy_wait工作负载(只有for(i = 0; i < N; ++i);(,但循环周期数不同。tp_workerbenchmark模式下工作,因此它将运行尽可能多的请求,并占用100%的CPU。

以下是配置示例:https://gist.github.com/myaut/ad946e89cb56b0d4acde

3.12(带有调试配置的香草(

EXP  |              MANAGER              |     WORKER
     |  sched            wait    service | sched            service
     |  policy           time     time   | policy            time
33   |  NORMAL          0.045    2.620   |     WAS NOT RUNNING
34   |  NORMAL          0.131    4.007   | NORMAL           125.192
35   |  NORMAL          0.123    4.007   | BATCH            125.143
36   |  NORMAL          0.026    4.007   | BATCH (nice=10)  125.296
37   |  NORMAL          0.025    3.978   | BATCH (nice=19)  125.223
38   |  FIFO (prio=9)  -0.022    3.991   | NORMAL           125.187
39   |  core:0:0        0.037    2.929   | !core:0:0        136.719

3.2(股票Debian(

EXP  |              MANAGER              |     WORKER
     |  sched            wait    service | sched            service
     |  policy           time     time   | policy            time
46   |  NORMAL          0.032    2.589   |     WAS NOT RUNNING
45   |  NORMAL          0.081    4.001   | NORMAL           125.140
47   |  NORMAL          0.048    3.998   | BATCH            125.205
50   |  NORMAL          0.023    3.994   | BATCH (nice=10)  125.202
48   |  NORMAL          0.033    3.996   | BATCH (nice=19)  125.223
42   |  FIFO (prio=9)  -0.008    4.016   | NORMAL           125.110
39   |  core:0:0        0.035    2.930   | !core:0:0        135.990

一些注意事项:

  • 所有时间均以毫秒为单位
  • 最后一个实验是设置亲和性(由@PhilippClaßen建议(:管理线程绑定到Core#0,而工作线程绑定到除Core[0]以外的所有核心
  • 管理器线程的服务时间增加了两倍,这可以通过内核内部的并发来解释(处理器有超线程!(
  • 使用SCHED_BACH+nice(TSLoad不能直接设置权重,但nice可以间接设置(略微减少了等待时间
  • SCHED_FIFO实验中的负等待时间是可以的:TSLoad保留了30us,这样它就可以做前期工作/调度器有时间做上下文切换等。看起来SCHED_FFIFO非常快
  • 保留单个核心并没有那么糟糕,而且由于它消除了核心内的并发性,服务时间显著减少

除了myout的答案之外,您还可以将管理器绑定到特定的CPU(sched_setafinity(,将工作程序绑定到其他CPU。当然,根据您的具体用例,这可能会非常浪费。

链接:线程绑定CPU核心

明确的让步通常是不必要的,事实上常常是不鼓励的。引用Robert Love在"Linux系统编程"中的话:

在实践中,在Linux等适当的抢占式多任务系统上,很少有合法使用sched_yield((的情况。内核完全能够做出最佳和最有效的调度决策——当然,内核比单个应用程序更适合决定抢占什么以及何时抢占。

他提到的例外情况是,当您正在等待外部事件时,例如,由用户、硬件或其他进程引起的事件。在你的例子中,情况并非如此。

myout的一个优秀答案是考虑尝试应用CONFIG_PREEMPT_RT补丁集的内核。这对内核的调度方式进行了一些相当重大的更改,最终结果是调度延迟变得更加具有确定性。

与myout的任何一个建议(尤其是与SCHED_FIFO(结合使用,可以获得非常好的结果。

最新更新