我目前正在开发一个大量多线程的应用程序,处理大量小数据批处理。
问题在于产生了过多的线程,这大大减慢了系统的速度。为了避免这种情况,我有一个Handles表来限制并发线程的数量。然后我“WaitForMultipleObjects”,当一个插槽被释放时,我创建了一个新的线程,并有自己的数据批处理。
现在,我拥有尽可能多的线程(通常每个核心一个)。即使这样,多线程产生的负载也是非常明智的。原因是:数据批量很小,所以我不断创建新的线程。
我目前正在实施的第一个想法就是将作业重新组合成更长的序列列表。因此,当我创建一个新线程时,它将在终止之前处理128或512个数据批处理。它效果很好,但有点破坏了粒度。
我被要求寻找另一个场景:如果问题来自“创建”线程太频繁,那么“暂停”它们,加载数据批处理并“恢复”线程呢?
不幸的是,我不太成功。 问题是:当线程处于“挂起”模式时,“WaitForMultipleObjects”不会将其检测为可用。实际上,我无法有效区分活动和挂起的线程。
所以我有两个问题:
如何检测“暂停的线程”,以便我可以将新数据加载到其中并恢复它?
这是个好主意吗?毕竟,“CreateThread”真的是一个资源猪吗?
修改
经过多次测试后,这里有关于线程池和IO完成端口的调查结果,这两个都在本文中提供了建议。
使用旧版本“QueueUserWorkItem”测试线程池。 IO完成端口需要使用CreateIoCompletionPort,GetQueuedCompletionStatus和PostQueuedCompletionStatus;
1)首先是性能:创建多个线程非常昂贵,并且线程池和io完成端口都可以很好地避免这种成本。我现在每批8个工作岗位,从每批512个工作岗位开始,没有减速。这是相当可观的。即使每批次单工作,性能影响也不到5%。非常了不起。
从性能角度来看,QueueUserWorkItem虽然获得了这么小的差距(大约1%更好)但几乎可以忽略不计。
2)关于使用简单性: 关于启动线程:毫无疑问,QueueUserWorkItem是迄今为止最容易设置的。相比之下,IO完成端口是重量级的。 关于结束线程:赢得IO完成端口。 由于某些未知原因,MS在C中不提供任何功能来了解何时使用QueueUserWorkItem完成所有作业。它需要一些讨厌的技巧来成功实现这个基本但关键的功能。这种缺乏功能是没有理由的。
3)关于资源控制:IO完成端口的 Big win ,允许精细调整并发线程数,而QueueUserWorkItem没有这样的控制,这将很乐意花费所有CPU周期从所有可用的核心。这本身可能是QueueUserWorkItem的交易破坏者。 请注意,较新版本的Completion Port似乎允许该控件,但仅适用于Windows Vista及更高版本。
4)兼容性:IO完成端口小赢,自Windows NT4起可用。 QueueUserWorkItem仅在Windows 2000以后才存在。但这已经足够了。较新版本的完成端口对于Windows XP来说是不可行的。
可以猜到,我在两种解决方案之间非常紧密。他们都正确回答了我的需求。 对于一般情况,我建议I / O完成端口,主要用于资源控制。 另一方面,QueueUserWorkItem更容易设置。可惜的是,它要求程序员单独处理作业结束检测,从而失去了大多数这种简单性。
答案 0 :(得分:4)
请考虑使用CreateThreadpool(),而不是实现自己的。操作系统将为您完成工作,您不必担心做得对。
答案 1 :(得分:3)
是的,CreateThread涉及相当多的开销。一种解决方案是使用线程池QueueUserWorkItem。另一种方法是启动一组线程并让它们从线程安全队列中检索“作业项”。
答案 2 :(得分:2)
如果您还想支持Windows XP,则无法使用CreateThreadpool - 否则,如果Vista和更新版本足够,则Windows线程池是最简单的方法。
如果需要Windows XP支持,则生成许多线程并将它们分配给IO completion port,然后在GetQueuedCompletionStatus()上使用每个线程块。完成端口允许您将事件发布到端口,该事件将为每个事件准确唤醒一个线程,并且它们非常高效。他们使用LIFO策略唤醒线程以保持缓存温暖。
在任何情况下,您都从不想要暂停线程。永远不能。阻止,等待,但不要暂停。
原因是暂停后你会遇到你描述的问题,而且你会产生死锁,例如:如果你的线程在临界区或互斥锁中。除了调试器之外,没有人需要暂停线程。