实现一个简单的线程池

时间:2012-03-06 09:12:04

标签: c++ windows multithreading threadpool

我目前需要一个简单而有效的线程池实现。我在这里搜索过,也在Google上搜索过,发现了许多有趣的链接,但到目前为止我发现的任何内容似乎都不合适。我在网上找到的大多数实现要么太复杂,要么缺少我需要的一些关键功能。

此外,我不想使用我不理解的代码,因此我决定自己编写代码(有时重新发明轮子可以帮助我在知识和经验方面向前推进)。我当然理解了线程池背后的基本思想,但是一些实现细节对我来说仍然有点不清楚。这可能是因为我需要的线程池类型有点特殊。让我来形容一下吧。我有一个在特定(大)缓冲区上完成数十万次的任务。我已经测量过,如果我使用线程执行此任务,性能会好得多 - 缓冲区被拆分为子缓冲区,每个线程在子缓冲区上执行其任务并返回结果。然后将所有线程的所有结果加在一起,为我提供最终解决方案。

然而,由于创建了这么多线程(由于线程创建带来的开销),因此经常这样做会丢失宝贵的时间。所以我想拥有一个可以执行此任务的线程池,而不是每次都创建一组新的线程。

更清楚的是,这是我到目前为止所做的:

  • 将缓冲区拆分为N个相同大小的子缓冲区
  • 对于每个子缓冲区,创建一个线程并在子缓冲区
  • 上运行它
  • 等待所有线程完成(WaitForMultipleObjects),将结果添加到一起并使线程崩溃
  • 重复

我想要实现的是:

  • 将缓冲区拆分为N个相同大小的子缓冲区
  • 将每个子缓冲区分配给线程池(具有正好N个线程)的线程
  • 线程完成后,让它休眠直到另一个任务准备就绪
  • 当所有线程完成(和休眠)时,将它们产生的结果加在一起
  • 通过唤醒线程重复并为其分配新任务

正如您所看到的,这是一个特殊的线程池,因为我需要等待线程完成。基本上我想摆脱一直创建线程的开销,因为程序经历了数十万次迭代,因此它可以在其生命周期内创建和销毁数百万个线程。好消息是我不需要在线程之间进行任何同步,它们都会获得自己的数据和存储位置。但是我必须等到所有线程都完成并且我有最终解决方案,因为下一个任务取决于前一个任务的结果。

我的主要问题是线程管理:

  • 一旦新任务准备就绪,我如何让我的线程“睡眠”并唤醒它们?
  • 我如何等待所有线程完成?

我将不胜感激任何帮助。如果我不够清楚,也可以随意提问。谢谢!

4 个答案:

答案 0 :(得分:2)

对我而言,与线程通信的首选方式是通过条件变量。因为您可以定义所需的条件并在其发生变化时发出信号。在您的情况下,您可以将其与传递子缓冲区的队列组合,以便每个线程在队列为空时等待。然后可以将结果放在另一个队列上,其中管理队列正在等待,直到所有线程都将结果发布到队列(对此队列的引用作为请求与子缓冲区一起传递)

答案 1 :(得分:1)

您是否看过其他线程池实现?例如http://threadpool.sourceforge.net/。你想要完成的并不是什么新鲜事。使线程等待新任务的一种方法是阻塞互斥锁,并在另一个任务准备就绪时解除阻塞该互斥锁。您还可以让线程通过线程返回父级的某种通知来通知它们已完成。

在我的工作中,我一直在大量使用线程池/线程,并且一直使用ØMQ进行跨线程的通信,这允许线程在为新工作做好准备时阻止来自ØMQ的read()请求

通过一些研究和一点点时间和精力,你应该能够弄清楚如何构建或利用现有的框架/工具来构建你需要的东西。然后当你遇到问题的代码时,你可以回到SO。

答案 2 :(得分:0)

  

如何让我的线程“睡眠”并在新任务完成后唤醒它们   准备好了吗?

您使用互斥锁或信号量,具体取决于您的情况(在某些情况下您可能需要使用条件变量或自动/手动重新发送),使线程等待相互之间或唤醒当事情发生时。

  

我如何等待所有线程完成?

您必须在每个线程上使用join()等待它完成。因此,如果您有一组线程,您可能希望在每个仍在运行的线程上调用join。

作为一个单独的注释:线程池已经存在,你可以使用像Boost.Threadpool这样的东西,而不是重新发明轮子。

答案 3 :(得分:0)

'正如您所看到的,这是一个特殊的线程池,因为我需要等待线程完成。' - 不完全的。您希望处理作业中上一个任务的线程提供作业完成通知。完成通知是theadPool的正常功能,否则原始线程将无法处理完整的结果集。池通常同时处理多个任务/任务层次结构,因此完成通知方法应该是线程无关的 - 没有join()或任何类型的东西。此外,没有WaitForMultipleObject() - 使用难以管理的同步对象数组,并且限制为64个对象。

线程池通常有一个线程池在生产者 - 消费者队列上等待任务。这些任务通常都是从一些Ctask'提供线程池服务的类。完成理解和通知的机制就是其中之一。

生产者 - 消费者队列本质上是一个正常的'队列类受到互斥锁的多重访问和信号量的保护,用于计算队列中的任务和线程等待。池线程每个都传递给这个队列,它们永远循环,等待队列信号量,从锁定队列中弹出任务然后调用接收到的任务的run()方法。

每个任务都会将线程池作为数据成员加载,因为它已提交到池中。这允许任务在需要时提交更多任务。

通常通过调用作为任务成员的事件方法在某处通知每个任务的完成,在将任务提交到池之前由原始线程加载。

任务还应该有一个子任务原子倒计时整数和一个等待完成其他任务的事件。

在你的例子中,这有什么用?你可以有一个主要的'提交数组处理任务并等待它们全部完成的任务。

池中的线程应该多于核心。我建议两倍。

需要拆分数组,以便为​​每个部分使用单独的任务。有多少任务 - 足以使可用内核全部用完,但不会产生过多的上下文切换。对于任何合理大小的数组,假设64个任务是合理的分割 - 超过可用的典型处理器数量。此外,不应按顺序拆分任务以避免错误共享。

所以,这个'主要'数组处理任务。使用数组引用加载它并将其完成事件设置为指向某个表示事件的方法。将任务提交到池中,等待事件。

任务被加载到一个线程上。它的run()方法使用两个循环并创建32个数组处理任务,每个任务都有自己的起始索引和数组长度,但是具有非连续的起始索引。该任务使用自己继承的submit()方法将32个新任务中的每一个加载到池中。除了实际排队在线程上执行的任务之外,此submit()还会对完成计数整数进行原子递增,并在排队任务之前将任务的完成事件设置为私有完成事件。私有完成事件原子递减完成计数,并在事件为零时发出信号。提交所有32个阵列处理事件后,主要任务将等待私有完成事件。

因此,32个阵列处理任务在线程上运行。每次完成时,运行它的线程都会调用其完成事件,该事件会减少主任务中的完成计数整数。最后,最后一个数组处理任务完成,完成计数整数减少到零,因此表示运行主任务的线程正在等待的事件。主要任务调用自己的completin事件,因此发出主要任务发起者正在等待的事件的信号。

主要任务创建者在完全处理完阵列的情况下运行。

..或者,您可以使用已经有效的threadPool类,如其他人所建议的那样。

相关问题