关于c ++:用于执行具有不同优先级的任意任务的线程池

关于c ++:用于执行具有不同优先级的任意任务的线程池

Thread pool for executing arbitrary tasks with different priorities

我正在尝试为线程池设计一个对我的工作有很多设计要求的设计。对于工作软件而言,这是一个真正的问题,这是一项艰巨的任务。我有一个可行的实现,但是我想把它扔给SO,看看人们会想到什么有趣的想法,以便我可以与我的实现进行比较,看看它是如何堆叠的。我已尝试尽我所能来满足要求。

线程池需要执行一系列任务。任务可以是短期运行(<1秒)或长期运行(小时或天)。每个任务都有一个相关的优先级(从1 =非常低到5 =非常高)。任务可以在其他任务运行时的任何时间到达,因此,当它们到达时,线程池需要将其拾取并在线程可用时安排它们。

任务优先级完全与任务长度无关。实际上,如果不仅仅运行一个任务,就无法判断一个任务要花多长时间。

有些任务受CPU约束,而有些则受IO约束。事先无法确定给定的任务是什么(尽管我猜可能可以在任务运行时进行检测)。

线程池的主要目标是最大化吞吐量。线程池应有效地使用计算机的资源。理想情况下,对于受CPU约束的任务,活动线程数应等于CPU数。对于与IO绑定的任务,应分配的线程数应多于CPU数,以使阻塞不会过度影响吞吐量。最小化使用锁和使用线程安全/快速容器很重要。

通常,您应该以更高的CPU优先级运行更高优先级的任务(参考:SetThreadPriority)。较低优先级的任务不应"阻止"较高优先级的任务运行,因此,如果在所有低优先级任务正在运行时出现较高优先级的任务,则较高优先级的任务将开始运行。

任务具有与之关联的"最大正在运行的任务"参数。每种类型的任务一次最多只能运行这么多并发实例。例如,队列中可能包含以下任务:

  • A-1000个实例-低优先级-最大任务1
  • B-1000个实例-低优先级-最大任务1
  • C-1000个实例-低优先级-最大任务1

一个有效的实现只能同时(最多)运行1 A,1 B和1C。

它需要在Windows XP,Server 2003,Vista和Server 2008(最新Service Pack)上运行。

作为参考,我们可以使用以下接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
namespace ThreadPool
{
    class Task
    {
    public:
        Task();    
        void run();
    };

    class ThreadPool
    {    
    public:
        ThreadPool();
        ~ThreadPool();

        void run(Task *inst);
        void stop();
    };
}

因此,我们将选择什么作为此的基本构建块。 Windows有两个看起来很有希望的构建块:-I / O完成端口(IOCP)和异步过程调用(APC)。 这两种方法都使我们无需进行显式锁定就可以进行FIFO排队,并且在诸如调度程序的地方具有一定数量的内置OS支持(例如,IOCP可以避免某些上下文切换)。

APC可能更合适,但由于它们并不完全"透明",因此我们必须对它们稍加小心。 如果工作项执行可警告的等待(:: SleepEx,:: WaitForXxxObjectEx等),而我们不小心将APC调度到线程,则新调度的APC将接管该线程,暂停先前执行的APC,直到新的APC被 完成。 这对我们的并发要求不利,并且可能使堆栈溢出的可能性更大。


It needs to run on Windows XP, Server 2003, Vista and Server 2008 (latest service packs).

系统内置线程池的哪些功能使其不适合您的任务?如果要使用XP和2003,则不能使用新的闪亮的Vista / 2008池,但仍可以使用QueueUserWorkItem和好友。


@DrPizza:

OK, I'm still a bit unclear about how
you wish the priorities to work. If
the pool is currently running a task
of type A with maximal concurrency of
1 and low priority, and it gets given
a new task also of type A (and maximal
concurrency 1), but this time with a
high priority, what should it do?

这是一个棘手的任务,尽管在这种情况下,我认为只允许低优先级的任务完成就可以了。通常,我们不会看到许多具有不同线程优先级的相同类型的任务。在我们的模型中,实际上可以安全地暂停并稍后在明确定义的点重新启动任务(出于不同的原因),尽管这样做带来的复杂性可能不值得冒险。

通常,只有不同类型的任务才具有不同的优先级。例如:

  • 任务-1000个实例-低优先级
  • B任务-1000个实例-高优先级

假设A任务已经出现并正在运行,然后B任务已经到达,我们希望B任务能够或多或少地立即运行。


@DrPizza - this is a very good question, and one that strikes right to the heart of the problem. There are a few reasons why QueueUserWorkItem and the Windows NT thread pool was ruled out (although the Vista one does look interesting, maybe in a few years).

是的,看起来它已经在Vista中得到了增强,现在功能相当丰富。

好的,对于您希望优先级如何工作,我仍然不清楚。如果该池当前正在运行最大并发性为1且优先级较低的任务,并且为其分配了一个新任务,同时也是一个类型为A的任务(且最大并发性为1),但是这次具有高优先级,它应该怎么做? ?

挂起当前正在执行的A会很麻烦(它可能持有新任务需要执行的锁定,从而使系统死锁)。它不能产生第二个线程,而只能让它并行运行(允许的并发度仅为1)。但是它不能等到低优先级任务完成后才能执行,因为运行时是无限制的,这样做将允许低优先级任务阻止高优先级任务。

我的假设是您追求的是后者的行为?


@DrPizza-这是一个非常好的问题,而这恰恰是问题的核心。排除QueueUserWorkItem和Windows NT线程池的原因有几个(尽管Vista看上去确实很有趣,也许在几年后)。

首先,我们希望更好地控制它的启动和停止时间。我们已经听说,如果NT线程池认为任务运行很短,则不愿意启动新线程。我们可以使用WT_EXECUTELONGFUNCTION,但是我们真的不知道任务是长还是短

其次,如果线程池已被长时间运行的低优先级任务填满,那么高优先级的任务就不会及时运行。 NT线程池没有真正的任务优先级概念,因此我们不能执行QueueUserWorkItem并说"哦,顺便说一句,立即运行"。

第三,(根据MSDN)NT线程池与STA公寓模型不兼容。我不确定这意味着什么,但是我们所有的工作线程都在STA中运行。


推荐阅读