10

我有一台四核机器,想编写一些代码来解析一个利用所有四核的文本文件。文本文件基本上每行包含一条记录。

多线程不是我的强项,所以我想知道是否有人可以给我一些我可以用来以最佳方式解析文件的模式。

我的第一个想法是将所有行读入某种队列,然后启动线程以将行从队列中拉出并处理它们,但这意味着队列必须存在于内存中,而且这些文件相当大,所以我我不那么热衷于这个想法。

我的下一个想法是拥有某种控制器,它将读取一行并为其分配一个线程来解析,但我不确定如果线程处理行的速度比它可以更快,控制器是否最终会成为瓶颈阅读并分配它们。

我知道可能还有比这两个更简单的解决方案,但目前我只是没有看到它。

4

7 回答 7

9

我会同意你原来的想法。如果您担心队列可能会变得太大,请为它实现一个缓冲区(即如果超过 100 行则停止读取文件,如果它低于 20 行则重新开始读取。您需要进行一些测试找到最佳障碍)。使任何线程都可能成为“读取器线程”,因为它必须锁定队列才能拉出一个项目,它还可以检查“低缓冲区”是否已被击中并再次开始读取。当它这样做时,其他线程可以读出队列的其余部分。

或者,如果您愿意,让一个读取器线程将这些行分配给其他三个处理器线程(通过它们自己的队列)并实施工作窃取策略。我从来没有这样做过,所以我不知道这有多难。

于 2008-08-10T03:19:36.623 回答
9

Mark 的回答是更简单、更优雅的解决方案。如果没有必要,为什么要构建具有线程间通信的复杂程序?产生 4 个线程。每个线程计算文件大小/4 以确定它的起点(和终点)。然后每个线程可以完全独立地工作。

添加一个特殊线程来处理读取的唯一原因是,如果您希望某些行需要很长时间来处理,并且您希望这些行聚集在文件的单个部分中。在不需要时添加线程间通信是一个非常糟糕的主意。您大大增加了引入意外瓶颈和/或同步错误的机会。

于 2008-08-10T05:21:43.567 回答
3

这将消除单线程读取的瓶颈:

open file
for each thread n=0,1,2,3:
    seek to file offset 1/n*filesize
    scan to next complete line
    process all lines in your part of the file
于 2008-08-10T04:41:43.367 回答
1

我的经验是使用 Java,而不是 C#,如果这些解决方案不适用,我深表歉意。

我能想到的直接解决方案是拥有一个运行 3 个线程的执行程序(例如,使用 )。对于从输入文件中读取的每一行/记录,在执行器上启动一个作业(使用)。执行器将为您排队请求,并在 3 个线程之间分配。Executors.newFixedThreadPoolExecutorService.submit

可能存在更好的解决方案,但希望能够完成这项工作。:-)

ETA:听起来很像 Wolfbyte 的第二个解决方案。:-)

ETA2:System.Threading.ThreadPool在 .NET 中听起来像是一个非常相似的想法。我从未使用过它,但它可能值得你花时间!

于 2008-08-10T03:43:22.720 回答
1

由于瓶颈通常在处理中,而不是在处理文件时的读取中,所以我会采用生产者-消费者模式。为了避免锁定,我会查看无锁定列表。由于您使用的是 C#,因此您可以查看 Julian Bucknall 的Lock-Free List代码。

于 2008-08-11T05:04:40.607 回答
0

@lomaxx

@Derek & Mark:我希望有办法接受 2 个答案。我最终将不得不使用 Wolfbyte 的解决方案,因为如果我将文件拆分为 n 个部分,则线程可能会遇到一批“慢”事务,但是如果我正在处理每个进程的文件保证需要等量的处理,那么我真的很喜欢您的解决方案,即只需将文件拆分为块并将每个块分配给一个线程并完成它。

不用担心。如果集群“慢”事务是一个问题,那么排队解决方案就是要走的路。根据平均事务的快慢,您可能还想考虑一次将多行分配给每个工作人员。这将减少同步开销。同样,您可能需要优化缓冲区大小。当然,这两个都是优化,您可能只应该在分析之后进行。(如果它不是瓶颈,那么担心同步是没有意义的。)

于 2008-08-11T00:02:20.607 回答
0

如果您正在解析的文本由重复的字符串和标记组成,请将文件分成块,并且对于每个块,您可以让一个线程将其预解析为由关键字、“标点符号”、ID 字符串和值组成的标记。字符串比较和查找可能非常昂贵,如果不必进行字符串查找和比较,将其传递给多个工作线程可以加速代码的纯逻辑/语义部分。

然后可以将预解析的数据块(您已经完成所有字符串比较并“标记化”它)传递给实际查看标记化数据的语义和排序的代码部分。

此外,您提到您担心占用大量内存的文件大小。您可以采取一些措施来减少内存预算。

将文件拆分成块并解析它。一次只读入与您正在处理的块一样多的块,再加上一些用于“预读”的块,因此当您在处理一个块之前,您不会在磁盘上停顿,然后再进入下一个块。

或者,可以对大文件进行内存映射并“按需”加载。如果处理文件的线程比 CPU 多(通常线程 = 1.5-2X CPU 是需求分页应用程序的一个好数字),为内存映射文件在 IO 上停止的线程将自动从操作系统停止,直到它们内存已准备就绪,其他线程将继续处理。

于 2008-09-17T04:27:08.297 回答