【问题标题】:Parallel.ForEach not spinning up new threadsParallel.ForEach 没有启动新线程
【发布时间】:2009-12-07 17:33:52
【问题描述】:

Parallel.ForEach 没有启动新线程

大家好,我们有一个非常 IO 密集型操作,我们使用 Microsoft 的 .NET Framework 并行扩展中的 Parallel.ForEach 编写。我们需要删除大量文件,我们将要删除的文件表示为列表的列表。每个嵌套列表中有 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 操作存在某种线程限制?

【问题讨论】:

  • 并行删除文件能获得多少收益?数额很大吗?您的底层硬件配置是否支持这样的收益?
  • DeleteMessageFiles的代码是什么
  • 如果您使用 Thread.ManagedThreadId 而不是 Thread.CurrentThread.Name,您会得到相同的结果吗?对于线程池线程,名称通常看起来相同,即使它们不是......
  • @roygbiv:这很好,如果“删除文件”是本地删除。如果以多线程方式完成,磁盘 IO 可能会更慢。在不知道“DeleteMessageFiles”做什么的情况下,很难说清楚。它可能会做其他大量的“工作”,在这种情况下,这将是一个很好的并发机会。
  • @roygbiv 我还不能说我们通过并行删除获得了多少,因为我们还没有成功地做到这一点。我上面发布的代码需要从网络存储中删除几百万个文件,所以这里有很多阻塞。在硬件配置方面,我只能推测我们的硬件会支持这样的增益,因为这是一个重型NAS。

标签: c# .net multithreading parallel-extensions


【解决方案1】:

有几种可能。

首先,在大多数情况下,Parallel.ForEach 不会产生新线程。它使用 .NET 4 ThreadPool(所有 TPL 都使用),并将重用 ThreadPool 线程。

话虽如此,Parallel.ForEach 使用基于传递给它的 List 大小的分区策略。我的第一个猜测是你的“外部”列表有很多消息,但内部列表只有一个 Message 实例,所以 ForEach 分区器只使用一个线程。有了一个元素,Parallel 就足够聪明,可以只使用主线程,而不是将工作转入后台线程。

通常,在这种情况下,最好并行化外循环,而不是内循环。这通常会给您带来更好的性能(因为您将拥有更大的工作项),尽管如果不很好地了解循环大小和工作单元的大小,就很难知道。您还可以潜在地并行化内部循环和外部循环,但是如果不进行分析,就很难判断什么是最佳选择。

另一种可能性:

尝试使用[Thread.ManagedThreadId][1] 而不是 Thread.CurrentThread.Name 进行日志记录。由于Parallel 使用ThreadPool 线程,因此“名称”通常在多个线程中是相同的。你可能认为你只使用了一个线程,而实际上你使用了多个线程......

【讨论】:

    【解决方案2】:

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

    假设您有一个类Person,其方法名为RaiseArm()。您总是可以尝试在 100 个不同的线程上射击 RaiseArm(),但 Person 一次只能引发两个...

    就像我说的,我可能是错的。这只是我的怀疑。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-11-27
      • 1970-01-01
      • 2021-08-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-04-08
      相关资源
      最近更新 更多