【发布时间】:2011-07-09 23:25:21
【问题描述】:
我目前正在开发一个高度多线程的应用程序,处理大量要处理的小数据批处理。
它的问题是产生了太多线程,这会大大降低系统速度。为了避免这种情况,我有一个句柄表,它限制了并发线程的数量。然后我“WaitForMultipleObjects”,当一个槽被释放时,我创建一个新线程,用它自己的数据批处理来处理。
现在,我拥有任意数量的线程(通常每个内核一个)。即便如此,多线程带来的负载也是非常合理的。这样做的原因:数据批量很小,所以我不断地创建新线程。
我目前正在实施的第一个想法是将作业重新组合成更长的序列列表。因此,当我创建一个新线程时,它会在被终止之前处理 128 或 512 个数据批处理。它运行良好,但在某种程度上破坏了粒度。
我被要求寻找另一种情况:如果问题来自过于频繁地“创建”线程,那么“暂停”它们、加载数据批处理并“恢复”线程呢?
不幸的是,我不太成功。 问题是:当线程处于“挂起”模式时,“WaitForMultipleObjects”不会检测到它是可用的。事实上,我无法有效地区分活动线程和挂起线程。
所以我有两个问题:
如何检测“挂起的线程”,以便我可以将新数据加载到其中并恢复它?
这是个好主意吗?毕竟,“CreateThread”真的是资源猪吗?
编辑
经过大量测试,这是我关于线程池和 IO 完成端口的发现,这两个都在这篇文章中提出。
使用旧版本“QueueUserWorkItem”测试线程池。 IO Completion Port 需要使用 CreateIoCompletionPort、GetQueuedCompletionStatus 和 PostQueuedCompletionStatus;
1) 首先是性能:创建许多线程的成本非常高,线程池和 io 完成端口都在避免这种成本方面做得很好。我现在每批 8 个作业,从之前的每批 512 个作业,没有减速。这是相当可观的。即使每批执行 1 个作业,性能影响也小于 5%。真是了不起。
从性能的角度来看,QueueUserWorkItem 胜出,尽管差距很小(大约高出 1%),几乎可以忽略不计。
2) 关于使用简单性: 关于启动线程:毫无疑问,QueueUserWorkItem 是迄今为止最容易设置的。相比之下,IO Completion 端口是重量级的。 关于结束线程:赢得 IO 完成端口。 由于某些未知的原因,MS 在 C 中没有提供函数来知道所有作业何时使用 QueueUserWorkItem 完成。它需要一些讨厌的技巧才能成功实现这个基本但关键的功能。这种缺乏功能没有任何借口。
3) 关于资源控制:IO Completion Port Big win,允许微调并发线程数,而 QueueUserWorkItem 没有这样的控制,它将愉快地花费所有 CPU 周期从所有可用的核心。这本身可能会破坏 QueueUserWorkItem。 请注意,较新版本的完成端口似乎允许该控制,但仅适用于 Windows Vista 及更高版本。
4) 关于兼容性:IO 完成端口的小胜利,从 Windows NT4 开始可用。 QueueUserWorkItem 仅存在于 Windows 2000 之后。但这已经足够了。更新版本的完成端口不适用于 Windows XP。
可以猜到,我在这两种解决方案之间非常纠结。他们都正确回答了我的需求。 对于一般情况,我建议 I/O Completion Port,主要用于资源控制。 另一方面,QueueUserWorkItem 更容易设置。很遗憾,它在要求程序员单独处理作业结束检测时失去了大部分这种简单性。
【问题讨论】:
标签: c windows multithreading