简体   繁体   English

挂起和恢复线程(Windows,C)

[英]Suspend and Resume thread (Windows, C)

I'm currently developing a heavily multi-threaded application, dealing with lots of small data batch to process. 我目前正在开发一个高度多线程的应用程序,处理大量小数据批处理。

The problem with it is that too many threads are being spawns, which slows down the system considerably. 它的问题是太多的线程正在产生,这大大减慢了系统的速度。 In order to avoid that, I've got a table of Handles which limits the number of concurrent threads. 为了避免这种情况,我有一个Handles表来限制并发线程的数量。 Then I "WaitForMultipleObjects", and when one slot is being freed, I create a new thread, with its own data batch to handle. 然后我“WaitForMultipleObjects”,当一个插槽被释放时,我创建一个新的线程,其中有自己的数据批处理。

Now, I've got as many threads as I want (typically, one per core). 现在,我拥有尽可能多的线程(通常每个核心一个)。 Even then, the load incurred by multi-threading is extremely sensible. 即使这样,多线程产生的负载也是非常明智的。 The reason for this: the data batch is small, so I'm constantly creating new threads. 原因是:数据批量很小,所以我不断创建新的线程。

The first idea I'm currently implementing is simply to regroup jobs into longer serial lists. 我目前正在实施的第一个想法就是将作业重新组合成更长的串行列表。 Therefore, when I'm creating a new thread, it will have 128 or 512 data batch to handle before being terminated. 因此,当我创建一个新线程时,它将在终止之前处理128或512个数据批处理。 It works well, but somewhat destroys granularity. 它运行良好,但有点破坏粒度。

I was asked to look for another scenario: if the problem comes from "creating" threads too often, what about "pausing" them, loading data batch and "resuming" the thread? 我被要求寻找另一个场景:如果问题来自“创建”线程太频繁,那么“暂停”它们,加载数据批处理和“恢复”线程呢?

Unfortunately, I'm not too successful. 不幸的是,我不太成功。 The problem is: when a thread is in "suspend" mode, "WaitForMultipleObjects" does not detect it as available. 问题是:当线程处于“挂起”模式时,“WaitForMultipleObjects”不会将其检测为可用。 In fact, I can't efficiently distinguish between an active and suspended thread. 实际上,我无法有效区分活动和挂起的线程。

So I've got 2 questions: 所以我有两个问题:

  1. How to detect "suspended thread", so that i can load new data into it and resume it? 如何检测“挂起的线程”,以便我可以将新数据加载到其中并恢复它?

  2. Is it a good idea? 这是个好主意吗? After all, is "CreateThread" really a ressource hog? 毕竟,“CreateThread”真的是一个资源猪吗?

Edit 编辑

After much testings, here are my findings concerning Thread Pooling and IO Completion Port, both advised in this post. 经过多次测试后,这里有关于线程池和IO完成端口的我的发现,这两个都在本文中提供了建议。

Thread Pooling is tested using the older version "QueueUserWorkItem". 使用旧版本“QueueUserWorkItem”测试线程池。 IO Completion Port requires using CreateIoCompletionPort, GetQueuedCompletionStatus and PostQueuedCompletionStatus; IO完成端口需要使用CreateIoCompletionPort,GetQueuedCompletionStatus和PostQueuedCompletionStatus;

1) First on performance : Creating many threads is very costly, and both thread pooling and io completion ports are doing a great job to avoid that cost. 1)首先是性能:创建多个线程非常昂贵,并且线程池和io完成端口都可以很好地避免这种成本。 I am now down to 8-jobs per batch, from an earlier 512-jobs per batch, with no slowdown. 我现在每批8个工作岗位,从每批512个工作岗位开始,没有减速。 This is considerable. 这是相当可观的。 Even when going to 1-job per batch, performance impact is less than 5%. 即使每批次单工作,性能影响也不到5%。 Truly remarkable. 非常了不起。

From a performance standpoint, QueueUserWorkItem wins, albeit by such a small margin (about 1% better) that it is almost negligible. 从性能的角度来看,QueueUserWorkItem虽然获得了如此小的利润(大约1%更好),但几乎可以忽略不计。

2) On usage simplicity : Regarding starting threads : No question, QueueUserWorkItem is by far the easiest to setup. 2)关于使用简单性:关于启动线程:毫无疑问,QueueUserWorkItem是迄今为止最容易设置的。 IO Completion port is heavyweight in comparison. 相比之下,IO完成端口是重量级的。 Regarding ending threads : Win for IO Completion Port. 关于结束线程:赢得IO完成端口。 For some unknown reason, MS provides no function in C to know when all jobs are completed with QueueUserWorkItem. 由于某些未知原因,MS在C中不提供任何功能来了解何时使用QueueUserWorkItem完成所有作业。 It requires some nasty tricks to successfully implement this basic but critical function. 它需要一些讨厌的技巧来成功实现这个基本但关键的功能。 There is no excuse for such a lack of feature. 这种缺乏功能没有任何借口。

3) On resource control : Big win for IO Completion Port, which allows to finely tune the number of concurrent threads, while there is no such control with QueueUserWorkItem, which will happily spend all CPU cycles from all available cores. 3)关于资源控制:IO完成端口的大赢 ,它允许精细调整并发线程的数量,而QueueUserWorkItem没有这样的控制,它将很乐意花费所有可用内核的所有CPU周期。 That, in itself, could be a deal breaker for QueueUserWorkItem. 这本身可能是QueueUserWorkItem的交易破坏者。 Note that newer version of Completion Port seems to allow that control, but are only available on Windows Vista and later. 请注意,较新版本的Completion Port似乎允许该控件,但仅适用于Windows Vista及更高版本。

4) On compatibility : small win for IO Completion Port, which is available since Windows NT4. 4)兼容性:IO完成端口小赢,自Windows NT4起可用。 QueueUserWorkItem only exists since Windows 2000. This is however good enough. QueueUserWorkItem仅在Windows 2000以后才存在。但这已经足够了。 Newer version of Completion Port is a no-go for Windows XP. 较新版本的完成端口对于Windows XP来说是不行的。

As can be guessed, I'm pretty much tied between the 2 solutions. 可以猜到,我非常依赖两种解决方案。 They both answer correctly to my needs. 他们都正确回答了我的需求。 For a general situation, I suggest I/O Completion Port, mostly for resource control. 对于一般情况,我建议I / O完成端口,主要用于资源控制。 On the other hand, QueueUserWorkItem is easier to setup. 另一方面,QueueUserWorkItem更容易设置。 Quite a pity that it loses most of this simplicity on requiring the programmer to deal alone with end-of-jobs detection. 可惜的是,它要求程序员单独处理作业结束检测,从而失去了大多数这种简单性。

Instead of implementing your own, consider using CreateThreadpool() . 不要实现自己的,而是考虑使用CreateThreadpool() The OS will do the work for you, and you don't have to worry about getting it right. 操作系统将为您完成工作,您不必担心正确。

Yes, there's a fair amount of overhead involved with CreateThread. 是的,CreateThread涉及相当多的开销。 One solution is to use a thread pool, QueueUserWorkItem . 一种解决方案是使用线程池QueueUserWorkItem Another is to just start a set of threads and have them retrieve a 'job item' from a thread-safe queue. 另一种方法是启动一组线程并让它们从线程安全队列中检索“作业项”。

If you want to also support Windows XP, you cannot use CreateThreadpool -- otherwise, if Vista and newer is sufficient, Windows thread pools are the easiest way. 如果您还想支持Windows XP,则不能使用CreateThreadpool - 否则,如果Vista和更新版本足够,Windows线程池是最简单的方法。

If Windows XP support is needed, spawn a number of threads and assign them to an IO completion port , then have each thread block on GetQueuedCompletionStatus(). 如果需要Windows XP支持,则生成许多线程并将它们分配给IO完成端口 ,然后在GetQueuedCompletionStatus()上使用每个线程块。 Completion ports let you post events to the port which will wake exactly one thread per event, and they are very efficient. 完成端口允许您将事件发布到端口,该事件将为每个事件准确唤醒一个线程,并且它们非常高效。 They use a LIFO strategy on waking threads to keep caches warm, too. 他们使用LIFO策略唤醒线程以保持缓存温暖。

In any case, you will never want to suspend a thread. 无论如何,你永远不会想要暂停一个线程。 Never ever. 永远不能。 Block, wait, but don't suspend. 阻止,等待,但不要暂停。

The reason is that with suspend you get the problem that you describe, plus you will create deadlocks, eg if your thread is within a critical section or mutex. 原因是暂停时会出现您描述的问题,而且您将创建死锁,例如,如果您的线程位于关键部分或互斥锁内。 Aside from a debugger, nobody should ever need to suspend a thread. 除了调试器之外,没有人需要暂停线程。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM