使用.NET进行多线程文件处理

Dar*_*der 16 .net c# architecture multithreading

有一个包含1000个小文本文件的文件夹.我的目标是解析和处理所有这些文件,同时将更多文件填充到文件夹中.我的目的是多线程这个操作,因为单线程原型花了六分钟来处理1000个文件.

我喜欢读写器线程如下.当读者线程正在读取文件时,我想让编写器线程来处理它们.一旦阅读器开始阅读文件,我想将其标记为正在处理,例如通过重命名.读完后,将其重命名为已完成.

我如何处理这样的多线程应用程序?

使用分布式哈希表或队列更好吗?

我使用哪种数据结构可以避免锁定?

这个方案有更好的方法吗?

Nic*_*ver 26

由于对评论中.NET 4的工作方式有好奇心,所以这就是这种方法.对不起,OP可能不是一个选项.免责声明:这不是一个高度科学的分析,只是表明有明显的性能优势.基于硬件,您的里程可能差异很大.

这是一个快速测试(如果你看到这个简单测试中的一个大错误,它只是一个例子.请评论,我们可以修复它更有用/准确).为此,我只是将12,000~60 KB的文件作为样本放入目录中(启动LINQPad ;你可以自己玩它,免费! - 确保获得LINQPad 4):

var files = 
Directory.GetFiles("C:\\temp", "*.*", SearchOption.AllDirectories).ToList();

var sw = Stopwatch.StartNew(); //start timer
files.ForEach(f => File.ReadAllBytes(f).GetHashCode()); //do work - serial
sw.Stop(); //stop
sw.ElapsedMilliseconds.Dump("Run MS - Serial"); //display the duration

sw.Restart();
files.AsParallel().ForAll(f => File.ReadAllBytes(f).GetHashCode()); //parallel
sw.Stop();
sw.ElapsedMilliseconds.Dump("Run MS - Parallel");
Run Code Online (Sandbox Code Playgroud)

大多数 简单情况下,只需略微更改循环以并行化查询即可 .通过"简单",我主要意味着一个动作的结果不会影响下一个动作.事情要记住最常见的是,一些收藏品,例如我们方便的List<T>不是线程安全的,所以使用它在一个并行场景不是一个好主意:)幸运的是在.NET 4中添加并发集合是线程安全的.另外请记住,如果您使用锁定集合,这可能也是一个瓶颈,具体取决于具体情况.

这使用.NET 4.0中提供的.AsParallel<T>(IEnumeable<T>).ForAll<T>(ParallelQuery<T>)扩展.该.AsParallel()调用包含IEnumerable<T>在一个实现的ParallelEnumerableWrapper<T>(内部类)中ParallelQuery<T>.这现在允许您使用并行扩展方法,在这种情况下我们正在使用.ForAll().

.ForAll()在内部打包ForAllOperator<T>(query, action)并同步运行它.这会在线程运行之后处理线程的线程和合并......在那里有相当多的进展,如果你想了解更多,我建议从这里开始,包括其他选项.


结果(计算机1 - 物理硬盘):

  • 型号:1288 - 1333ms
  • 平行:461 - 503ms

电脑规格 - 用于比较:

结果(计算机2 - 固态硬盘):

  • 型号:545 - 601 ms
  • 平行:248 - 278毫秒

电脑规格 - 用于比较:

这次我没有CPU/RAM的链接,这些已经安装好了.这是戴尔M6400笔记本电脑(这里是M6500链接 ......戴尔自己的6400链接损坏).


这些数字来自10次运行,取内部8个结果的最小值/最大值(删除每个可能的异常值的原始最小值/最大值).我们在这里遇到了I/O瓶颈,特别是在物理驱动器上,但想想串行方法的作用.它读取,处理,读取,处理,冲洗重复.使用并行方法,您(即使有I/O瓶颈)同时读取和处理.在最糟糕的瓶颈情况下,您正在处理一个文件,同时阅读下一个文件.仅此一点(在任何当前的计算机上!)应该会带来一些性能提升.你可以看到我们在上面的结果中一次可以得到一个以上,给我们一个健康的提升.

另一个免责声明:四核+ .NET 4并行不会给你四倍的性能,它不会线性扩展...还有其他考虑因素和瓶颈在起作用.

我希望这有兴趣展示方法和可能的好处.随意批评或改进...这个答案仅存在于评论中指出的好奇者:)


Kir*_*ril 6

设计

生产者/消费者模式可能对这种情况最有用.您应该创建足够的线程以最大化吞吐量.

以下是有关制作人/消费者模式的一些问题,以便您了解其工作原理:

您应该使用阻塞队列,并且生产者应该在消费者处理队列中的文件时将文件添加到队列中.阻塞队列不需要锁定,因此它是解决问题的最有效方法.

如果您使用的是.NET 4.0,则可以使用多个并发集合:

穿线

单个生产者线程可能是从磁盘加载文件并将其推送到队列的最有效方式; 随后,多个消费者将从队列中弹出项目,他们将处理它们.我建议您每个核心尝试2-4个消费者线程,并进行一些性能测量,以确定哪个是最优的(即为您提供最大吞吐量的线程数).我建议在这个特定的例子中使用ThreadPool.

PS我不明白单点故障和分布式哈希表的使用是什么?我知道DHT听起来真的很酷,但我会首先尝试传统方法,除非你有一个特定的问题,你想要解决.