9

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

它需要在 Windows XP、Server 2003、Vista 和 Server 2008(最新的服务包)上运行。


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

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

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

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

5 回答 5

5

那么我们将选择什么作为基本构建块。Windows 有两个看起来很有前途的构建块:- I/O 完成端口 (IOCP) 和异步过程调用 (APC)。这两者都为我们提供了 FIFO 队列,而无需执行显式锁定,并且在诸如调度程序之类的地方具有一定数量的内置操作系统支持(例如,IOCP 可以避免某些上下文切换)。

APC 可能更适合一些,但我们必须对它们稍加小心,因为它们不是很“透明”。如果工作项执行了一个警报等待(::SleepEx、::WaitForXxxObjectEx 等)并且我们不小心向线程分派了一个 APC,那么新分派的 APC 将接管该线程,挂起先前执行的 APC 直到新的 APC完成的。这对我们的并发性要求不利,并且更有可能导致堆栈溢出。

于 2008-09-02T18:51:57.673 回答
1

它需要在 Windows XP、Server 2003、Vista 和 Server 2008(最新的服务包)上运行。

系统内置线程池的哪些特性使其不适合您的任务?如果你想以 XP 和 2003 为目标,你不能使用新的闪亮的 Vista/2008 池,但你仍然可以使用 QueueUserWorkItem 和朋友。

于 2008-09-01T21:58:55.750 回答
0

@DrPizza - 这是一个非常好的问题,并且直击问题的核心。QueueUserWorkItem 和 Windows NT 线程池被排除在外有几个原因(尽管 Vista 看起来确实很有趣,也许在几年之后)。

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

其次,如果线程池已经被长时间运行的低优先级任务填满,那么高优先级任务就没有机会及时运行。NT 线程池没有真正的任务优先级概念,所以我们不能做一个 QueueUserWorkItem 并说“顺便说一句,马上运行这个”。

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

于 2008-09-01T22:18:26.500 回答
0

@DrPizza - 这是一个非常好的问题,并且直击问题的核心。QueueUserWorkItem 和 Windows NT 线程池被排除在外有几个原因(尽管 Vista 看起来确实很有趣,也许在几年之后)。

是的,看起来它在 Vista 中得到了相当的加强,现在非常通用。

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

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

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

于 2008-09-01T22:37:18.763 回答
0

@DrPizza:

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

这个有点棘手,尽管在这种情况下,我想我会很高兴让低优先级任务运行完成。通常,我们不会看到很多具有不同线程优先级的相同类型的任务。在我们的模型中,实际上可以在某些明确定义的点(出于不同的原因)安全地停止并稍后重新启动任务,尽管这可能带来的复杂性可能不值得冒险。

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

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

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

于 2008-09-01T22:46:13.500 回答