.NET Parallel.Foreach 如何使用 IO 密集型操作构建

问题描述

比如说,我编写了一个程序,它应该读取给定中的所有文本文件并从中生成一个对象列表。

步骤是

  1. 从磁盘读取给定文件夹中所有文件文件内容
  2. 为每个文件内容创建唯一的对象。

我在质疑 .NET 中的 Parallel.ForEach(或任何其他并行构造)是否会提高步骤 1 的性能以及如何提高性能。磁盘的 IO 本质上不是同步的,即磁盘的磁头不能同时位于 5 个位置。事实上,它可能会使事情变慢?

您对此有何看法?

解决方法

让我们区分两个不同的概念:

  • 并发:一次做不止一件事。
  • 并行:通过在多个并发运行的线程之间划分来完成大量工作。

(这些定义来自Stephen Cleary's Concurrency in C# Cookbook。)

并发不需要多个执行器。它可以使用单个任务,并且可以使用上下文切换来在每项任务上取得进展。 (在给定的时间点,它会暂停给定任务的执行并切换到另一个作业。)

另一方面,当我们谈论并行处理时,我们可以假设有多个可用的执行器,这就是为什么多个作业可以同时取得进展的原因。

Concurrent vs Parallel
Source


在 .NET 的情况下,当我们谈论并行编程时,我们大部分时间都指的是 CPU bound operations。这就是 Parallel.ForeachParallel.ForParallel.Invoke 专为多线程设计的原因。

如果您访问 related MSDN article,那么第一眼就会产生误导。它使用一个从给定文件夹读取文件的示例。不过请注意这条评论:

Parallel.ForEach(files,(currentFile) =>
{
    // The more computational work you do here,the greater the speedup compared to a sequential foreach loop.

因此,根据您想做的工作,Parallel.XYZ 或 PLinq 可能不是最佳选择。如果您想同时执行多个异步 I/O 操作,那么 Task.WhenAll 是您最好的朋友。


如果您想更好地理解并行编程,那么我建议您阅读 Stephen Toub 的优秀白皮书:Patterns of Parallel Programming C#

我也鼓励您观看 Jeffry Richter 关于 async I/Oscalable applications 的演示。

,

首先,SSD 现在无处不在,并且不会像旧 HDD 那样受到随机读取的影响。 RAID 设置(或 SAN)也可以执行大型并行操作。

但是如果您基本上是 IO 绑定,即您正在等待磁盘并且没有进行大量处理,那么不,并行运行没有意义.唯一会发生的就是你会有更多的等待线程,正如你所说,也会有更多的随机读取和更少的顺序。

让一个线程只做 I/O 而另一对做处理可能是有意义的,这样 I/O 队列深度保持很深,因为新的请求不断地通过。


如果您的代码在 CPU 上占用大量资源并且没有受到 I/O 的限制,那就是当您使用完全并行性时。例如解压、加密和解密。