Parallel.ForEach没有启动新线程

3

Parallel.ForEach不会启动新线程

大家好,我们编写了一个使用Microsoft的Parallel Extensions for the .NET Framework中的Parallel.ForEach进行IO密集型操作的程序。我们需要删除大量文件,并将要删除的文件表示为列表嵌套列表。每个嵌套列表中有1000条消息,我们有50个这样的列表。问题在于,当我查看日志时,我只看到一个线程在我们的Parallel.ForEach块内执行。

以下是代码示例:

List<List<Message>> expiredMessagesLists = GetNestedListOfMessages();
foreach (List<Message> subList in expiredMessagesLists)
{
    Parallel.ForEach(subList, msg =>
    {
        try
        {
            Logger.LogEvent(TraceEventType.Information, "Purging Message {0} on Thread {1}", msg.MessageID, msg.ExtensionID, Thread.CurrentThread.Name);

            DeleteMessageFiles(msg);
        }
        catch (Exception ex)
        {
            Logger.LogException(TraceEventType.Error, ex);
        }
    });
}

我用更简单的数据结构和没有IO逻辑的示例代码编写了一些代码,并可以看到在Parallel.ForEach块内执行了几个不同的线程。我们在上面的代码中使用Parallel.ForEach有什么不正确的地方吗?它可能是列表中的列表出了问题,或者IO操作存在某种线程限制。


4
同时删除文件能获得多少收益?这个收益量是否显著?你的底层硬件配置是否支持这样的收益? - user113476
如果您使用Thread.ManagedThreadId而不是Thread.CurrentThread.Name,是否会得到相同的结果?对于线程池线程,即使它们不同,名称通常看起来相同... - Reed Copsey
@roygbiv:这是一个非常好的观点,如果“Delete file”是本地删除的话。如果以多线程方式执行,则磁盘IO可能会变慢。不过,如果不知道“DeleteMessageFiles”的具体作用,就很难说了。如果它还有其他重要的工作需要完成,那么使用并发处理就是个不错的选择。 - Reed Copsey
@roygbiv 我们还不能确定并行删除能带来多大的收益,因为我们还没有成功地实现它。我上面发布的代码需要从网络存储中删除数百万个文件,所以这里有很多阻塞。就硬件配置而言,我只能猜测我们的硬件将支持这样的收益,因为这是一个重型NAS。 - user196968
@codypo:你有检查过内部列表的长度吗?你尝试过并行化外部循环吗? - Reed Copsey
显示剩余3条评论
2个回答

6
有几种可能性。
首先,在大多数情况下,Parallel.ForEach 不会生成新线程。它使用 .NET 4 线程池(所有 TPL 都是如此),并将重用线程池线程。
也就是说,Parallel.ForEach 使用基于传递给它的 List 大小的分区策略。我第一个猜测是,您的“外部”列表有很多消息,但内部列表只有一个 Message 实例,因此 ForEach 分区器仅使用一个线程。对于一个元素,Parallel 足够聪明,只使用主线程,而不将工作旋转到后台线程。
通常,在这种情况下,最好并行化外部循环,而不是内部循环。这通常会提高性能(因为您将具有更大的工作项),尽管没有很好地了解循环大小以及工作单元的大小很难知道。您还可以同时并行化内部和外部循环,但如果没有进行分析,很难确定哪个选项是最佳的。
另外一种可能性:
尝试使用 [Thread.ManagedThreadId][1] 而不是 Thread.CurrentThread.Name 进行日志记录。由于 Parallel 使用线程池线程,因此“名称”在多个线程之间通常是相同的。您可能认为只使用了一个线程,而实际上使用了多个线程...

2

你的代码基于这样一个假设,即可以并行删除文件。我不是说这不可能(我对此并不是专家),但我不会感到惊讶,如果对于大多数硬件来说,这根本不可能。毕竟,当你这样做时,你正在执行与物理对象(你的硬盘)相关的操作。

假设你有一个类叫做 Person,其中有一个名为 RaiseArm() 的方法。你可以尝试在100个不同的线程上同时运行 RaiseArm(),但是这个 Person 只能同时举起两只手...

就像我说的,我可能是错的。这只是我的猜测。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接