我正在尝试设计一个线程池,它对我的​​工作有很多设计要求。对于工作软件来说,这是一个真正的问题,而且是一项艰巨的任务。我有一个有效的实现,但我想把它扔给SO,看看人们能想出什么有趣的想法,这样我就可以与我的实现进行比较,看看它是如何叠加的。我已尽力尽可能具体地满足要求。

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

任务优先级完全独立于任务长度。事实上,如果不运行某个任务,就不可能知道它需要多长时间才能运行。

有些任务受 CPU 限制,而有些任务则受 IO 限制。不可能事先知道给定的任务是什么(尽管我猜想在任务运行时可能会检测到)。

线程池的主要目标是最大化吞吐量。线程池应该有效地利用计算机的资源。理想情况下,对于 CPU 密集型任务,活动线程的数量应等于 CPU 的数量。对于 IO 密集型任务,应分配比 CPU 数量更多的线程,以便阻塞不会过度影响吞吐量。尽量减少锁的使用并使用线程安全/快速容器非常重要。

一般来说,您应该以更高的 CPU 优先级运行更高优先级的任务(参考:设置线程优先级)。较低优先级任务不应“阻止”较高优先级任务运行,因此,如果在所有低优先级任务正在运行时出现较高优先级任务,则较高优先级任务将开始运行。

这些任务具有与其关联的“最大运行任务”参数。每种类型的任务一次最多只能运行该任务的多个并发实例。例如,我们的队列中可能有以下任务:

  • 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();
    };
}
有帮助吗?

解决方案

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

装甲运兵车可能更适合,但我们必须小心对待它们,因为它们不太“透明”。如果工作项执行可警报等待(::SleepEx、::WaitForXxxObjectEx 等),并且我们意外地向线程分派 APC,则新分派的 APC 将接管该线程,挂起先前执行的 APC,直到新的 APC 被执行。完成的。这不利于我们的并发要求,并且更有可能导致堆栈溢出。

其他提示

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

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

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

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

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

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

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

是的,看起来它在 Vista 中得到了很大的增强,现在非常通用。

好吧,我还是有点不清楚你希望优先事项如何运作。如果池当前正在运行最大并发数为 1 且优先级较低的 A 类型任务,并且它获得了一个同样为 A 类型(最大并发数为 1)的新任务,但这次具有高优先级,它应该做什么?

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

我的推测是您所追求的是后一种行为?

@Pizza博士:

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

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

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

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

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

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top