C++ 如何为';经理';和';工人';进程的线程?

C++ 如何为';经理';和';工人';进程的线程?,c++,linux,multithreading,scheduling,thread-priority,C++,Linux,Multithreading,Scheduling,Thread Priority,我正在运行一个进程(在基于Linux 3.x的操作系统上),其中: 一些线程是“管理器”线程(为简单起见,假设它们决定哪些工作线程应该做什么,但不做任何I/O,它们所需的CPU时间总量比工作线程短/短得多) 更多的线程是“工作者”线程:它们以智能方式执行繁重的计算,我对它们在任何时候被抢占都没有问题 可能存在超额订阅(即,在使用HT的英特尔处理器上,工作线程数超过内核数的两倍)。现在,我看到的是“管理器”线程没有足够频繁地获得处理器时间。他们并不是完全“挨饿”,我只是想给他们一点鼓励。所以,

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

  • 一些线程是“管理器”线程(为简单起见,假设它们决定哪些工作线程应该做什么,但不做任何I/O,它们所需的CPU时间总量比工作线程短/短得多)
  • 更多的线程是“工作者”线程:它们以智能方式执行繁重的计算,我对它们在任何时候被抢占都没有问题
可能存在超额订阅(即,在使用HT的英特尔处理器上,工作线程数超过内核数的两倍)。现在,我看到的是“管理器”线程没有足够频繁地获得处理器时间。他们并不是完全“挨饿”,我只是想给他们一点鼓励。所以,我自然地考虑过设置不同的线程优先级(我在Linux上),但后来我注意到线程调度器的不同选择及其效果。在这一点上,我感到困惑,或者更确切地说,我不清楚:

  • 我应该为经理选择哪种调度策略,为工人选择哪种调度策略
  • 我应该将线程优先级设置为什么(如果有)
  • 我是否需要偶尔让我的线程屈服()
注意事项:

  • 我有意不说任何关于语言或线程池机制的内容。我想在更一般的背景下问这个问题
  • 请不要对CPU核心进行假设。可能有很多,也可能只有一个,也许每个核心都需要工人(或工人和经理)
  • 工作线程可以执行I/O,也可以不执行I/O。不过,对于它们不执行任何I/O的情况,欢迎回答
  • 除了运行我的应用程序,我真的不需要系统具有很高的响应能力。我的意思是,我希望能够在那里使用SSH,并让我的输入在没有明显延迟的情况下得到回应,但没有真正的限制

    • UPD 12.02.2015:我做了一些实验

      理论 将“管理器”线程调度程序更改为RT(提供SCHED_截止日期/SCHED_FIFO策略的实时调度程序)显然是一种解决方案。在这种情况下,“管理器”线程总是比系统中的大多数线程具有更高的优先级,因此它们几乎总是在需要时获得CPU

      不过,还有另一个解决方案允许您继续使用CFS调度程序。您对“worker”线程的用途的描述类似于批处理调度(在古代,当计算机很大时,用户必须将其作业放入队列并等待数小时,直到其完成)。Linux CFS通过SCHED_批处理策略支持批处理作业,通过SCHED_正常策略支持对话框作业

      内核代码()中还有有用的注释:

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

      如果不更改调度程序策略,您的问题似乎无法解决。如果“worker”线程非常繁忙,而“manager”很少被唤醒,那么它们将获得相同的
      vruntime
      奖金,因此“worker”总是会抢占“manager”线程(但您可能会增加它们的权重,以便它们更快地耗尽奖金)

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

      有两个线程池:
      tpu-manager
      有4个线程和
      tpu-worker
      有30个线程,都运行
      busy\u-wait
      工作负载(仅
      for(i=0;i
      ),但循环次数不同
      tpu-worker
      工作在
      benchmark
      模式下,因此它将运行尽可能多的请求,并占用100%的CPU

      以下是示例配置:

      3.12(带有调试配置)

      3.2(股票借记卡)

      一些注意事项:

      • 所有时间均以毫秒为单位
      • 最后一个实验是设置亲缘关系(由@PhilippClaßen建议):管理线程绑定到核心0,而工作线程绑定到除核心0之外的所有核心
      • manager线程的服务时间增加了两倍,这可以通过内核内的并发性来解释(处理器具有超线程!)
      • 使用SCHED_BATCH+nice(TSLoad不能直接设置重量,但
        nice
        可以间接设置重量)稍微减少了等待时间
      • SCHED_FIFO实验中的负等待时间是可以的:TSLoad保留了30us,这样它可以做准备工作/调度器有时间进行上下文切换/等等。。看来SCHED_FIFO非常快
      • 保留单核并没有那么糟糕,因为它在核心并发中被删除,所以服务时间显著减少

      除了myaut的答案之外,您还可以将管理者绑定到特定的CPU(),将工作者绑定到其他CPU。当然,这取决于您的具体用例,可能非常浪费

      链接:

      显式屈服通常是不必要的,事实上通常是不鼓励的。引用Robert Love在“Linux系统编程”中的话:

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


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

      < P> > MyAut的优秀答案的补充是考虑尝试使用OpjyPrimeTytRT修补程序集应用的内核。这使得一些相当重要的改变如何
      /*
       * 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;
      
      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
      
      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