多核文本文件解析
我有一台四核计算机,想编写一些代码来解析利用所有四个核的文本文件。文本文件基本上每行包含一个记录。
多线程不是我的强项,所以我想知道是否有人可以给我一些我可以用来以最佳方式解析文件的模式。
我的第一个想法是将所有行读入某种队列,然后旋转线程以将这些行拉出队列并对其进行处理,但这意味着该队列将必须存在于内存中,并且这些文件都是有问题的大文件,因此我我不太喜欢这个主意。
我的下一个想法是要拥有某种控制器,该控制器将在一行中读取并为其分配一个要解析的线程,但是我不确定如果线程处理行的速度比该控制器快,那么该控制器是否最终会成为瓶颈?阅读并分配它们。
我知道可能有比这两种方法更简单的解决方案,但目前我还没有看到。
解决方案
回答
我会同意你的初衷。如果我们担心队列可能太大,请为其执行一个缓冲区(即,如果队列超过100行,则停止读取文件;如果队列超过20行,则重新开始读取。),我们需要进行一些测试找到最佳的障碍)。使其成为可能的任何线程都可能成为"阅读器线程",因为它必须锁定队列以将某项拉出,否则它还可以检查是否已击中"低缓冲区"并再次开始读取。在执行此操作时,其他线程可以读出其余队列。
或者,如果我们愿意,可以让一个读取器线程将这些行分配给其他三个处理器线程(通过它们自己的队列),并实施工作窃取策略。我从来没有做过,所以我不知道这有多难。
回答
我的经验是使用Java,而不是C#,因此,如果这些解决方案不适用,我深表歉意。
我可以想到的立即解决方案是让执行程序运行3个线程(例如,使用Executors..newFixedThreadPool
)。对于从输入文件中读取的每一行/记录,在执行器上触发一个作业(使用ExecutorService``.submit
)。执行程序将为我们排队请求,并在3个线程之间分配。
可能存在更好的解决方案,但希望能够完成这项工作。 :-)
ETA:听起来很像Wolfbyte的第二个解决方案。 :-)
ETA2:System.Threading.ThreadPool
听起来像.NET中的一个非常相似的想法。我从未使用过,但是值得我们花一会儿!
回答
这将消除使用单线程进行读取的瓶颈:
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
回答
马克的答案是更简单,更优雅的解决方案。如果没有必要,为什么要使用线程间通信来构建复杂的程序?产生4个线程。每个线程都会计算文件大小/ 4,以确定其起点(和终点)。然后,每个线程可以完全独立地工作。
添加特殊线程来处理读取的唯一原因是,如果我们希望某些行需要很长时间才能处理,并且我们希望这些行聚集在文件的单个部分中。在不需要时添加线程间通信是一个非常糟糕的主意。我们极大地增加了引入意外瓶颈和/或者同步错误的机会。
回答
@lomaxx
@Derek & Mark: I wish there was a way to accept 2 answers. I'm going to have to end up going with Wolfbyte's solution because if I split the file into n sections there is the potential for a thread to come across a batch of "slow" transactions, however if I was processing a file where each process was guaranteed to require an equal amount of processing then I really like your solution of just splitting the file into chunks and assigning each chunk to a thread and being done with it.
不用担心。如果群集的"慢速"事务是个问题,那么排队解决方案就是解决之道。根据平均事务处理的快慢,我们可能还需要查看一次为每个工作人员分配多行。这将减少同步开销。同样,我们可能需要优化缓冲区大小。当然,这两个都是我们可能仅在概要分析后才应该执行的优化。 (不必担心同步是否会成为瓶颈。)
回答
由于瓶颈通常在处理文件而不是读取文件时,我会采用生产者-消费者模式。为了避免锁定,我将查看无锁定列表。由于我们使用的是C,因此可以查看Julian Bucknall的无锁列表代码。
回答
如果要解析的文本由重复的字符串和标记组成,请将文件分成多个块,对于每个块,我们可以让一个线程将其预解析为由关键字,"标点符号",ID字符串和值组成的标记。字符串比较和查找可能非常昂贵,如果不必执行字符串查找和比较,则将其传递给多个工作线程可以加快代码的纯粹逻辑/语义部分。
然后,可以将预先准备好的数据块(我们已经完成所有字符串比较并对其进行"标记化")传递给代码部分,该部分实际上将查看标记化数据的语义和顺序。
另外,我们提到我们担心文件占用大量内存的大小。我们可以采取几项措施来减少内存预算。
将文件拆分为多个块并进行解析。一次只读取我们正在处理的块,再读取一些"预读"块,这样在处理完一个块之后再转到下一个块时,就不会在磁盘上停顿了。
或者,可以将大文件映射到内存并"按需"加载。如果我们正在处理文件的线程多于CPU(通常,线程= 1.5-2X CPU对于按需分页应用程序来说是一个很好的数目),则在IO上停滞的内存映射文件线程将自动从OS暂停,直到它们内存已准备就绪,其他线程将继续处理。