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

时间:2020-03-05 18:46:08  来源:igfitidea点击:

我正在尝试为线程池设计一个对我的工作有很多设计要求的设计。对于正常工作的软件来说,这是一个实际的问题,这是一项艰巨的任务。我有一个可行的实现,但是我想把它扔给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)上运行。

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

namespace ThreadPool
{
    class Task
    {
    public:
        Task();     
        void run();
    };

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

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

解决方案

回答

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

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

回答

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

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

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

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

回答

@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:

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任务能够或者多或者少地立即运行。

回答

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

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