处理多个进程中的单个文件


80

我有一个大文本文件,我想在其中处理每一行(执行一​​些操作)并将它们存储在数据库中。由于单个简单程序花费的时间太长,我希望它可以通过多个进程或线程来完成。每个线程/进程应从单个文件中读取不同的数据(不同的行),并对它们的数据(行)进行一些操作,然后将它们放入数据库中,以便最后,我处理完所有数据,数据库随我需要的数据一起转储了。

但是我无法弄清楚如何解决这个问题。


3
好问题。我也有这个疑问。尽管我选择了将文件分成小文件的选择:)
Sushant Gupta

Answers:


108

您正在寻找的是生产者/消费者模式

基本线程示例

这是使用线程模块的基本示例(而不是多处理)

import threading
import Queue
import sys

def do_work(in_queue, out_queue):
    while True:
        item = in_queue.get()
        # process
        result = item
        out_queue.put(result)
        in_queue.task_done()

if __name__ == "__main__":
    work = Queue.Queue()
    results = Queue.Queue()
    total = 20

    # start for workers
    for i in xrange(4):
        t = threading.Thread(target=do_work, args=(work, results))
        t.daemon = True
        t.start()

    # produce data
    for i in xrange(total):
        work.put(i)

    work.join()

    # get the results
    for i in xrange(total):
        print results.get()

    sys.exit()

您不会与线程共享文件对象。您可以通过为队列提供数据行来为他们工作。然后,每个线程将接起一行,对其进行处理,然后将其返回到队列中。

多处理模块中内置了一些更高级的功能来共享数据,例如列表和特殊的Queue。在使用多处理与线程时需要权衡取舍,这取决于您的工作是CPU约束还是IO约束。

基本的多处理池示例

这是一个多处理池的真正基本示例

from multiprocessing import Pool

def process_line(line):
    return "FOO: %s" % line

if __name__ == "__main__":
    pool = Pool(4)
    with open('file.txt') as source_file:
        # chunk the work into batches of 4 lines at a time
        results = pool.map(process_line, source_file, 4)

    print results

是管理其自身进程的便捷对象。由于打开的文件可以遍历其行,因此您可以将其传递给pool.map(),该文件将循环遍历并将行传递给worker函数。映射将阻止并在完成后返回整个结果。请注意,这是一个过于简化的示例,pool.map()在进行工作之前,它将立即将整个文件读入内存。如果您希望有大文件,请记住这一点。有更多高级方法可以设计生产者/消费者设置。

手动“池”,具有限制和行重新排序

这是Pool.map的手动示例,但是您可以设置队列大小,以使您仅以其可以处理的最快速度逐个喂入,而不是一次性消耗整个可迭代对象。我还添加了行号,以便以后可以跟踪它们并引用它们。

from multiprocessing import Process, Manager
import time
import itertools 

def do_work(in_queue, out_list):
    while True:
        item = in_queue.get()
        line_no, line = item

        # exit signal 
        if line == None:
            return

        # fake work
        time.sleep(.5)
        result = (line_no, line)

        out_list.append(result)


if __name__ == "__main__":
    num_workers = 4

    manager = Manager()
    results = manager.list()
    work = manager.Queue(num_workers)

    # start for workers    
    pool = []
    for i in xrange(num_workers):
        p = Process(target=do_work, args=(work, results))
        p.start()
        pool.append(p)

    # produce data
    with open("source.txt") as f:
        iters = itertools.chain(f, (None,)*num_workers)
        for num_and_line in enumerate(iters):
            work.put(num_and_line)

    for p in pool:
        p.join()

    # get the results
    # example:  [(1, "foo"), (10, "bar"), (0, "start")]
    print sorted(results)

1
很好,但是如果处理受I / O约束怎么办?在这种情况下,并行性可能会使速度变慢而不是加快速度。单个磁盘磁道内的寻道比磁道间寻道要快得多,并且并行执行I / O往往会在其他情况下会导致顺序I / O负载引入磁道间寻道。为了从并行I / O中获得一些好处,有时使用RAID镜像会有所帮助。
user1277476 2012年

2
@ jwillis0720-好的。 (None,) * num_workers创建一个None值的元组,其值等于工人人数的大小。这些将是指示每个线程退出的哨兵值,因为没有更多工作了。该itertools.chain函数使您可以将多个序列放到一个虚拟序列中,而不必复制任何内容。因此,我们得到的是,它首先循环遍历文件中的各行,然后遍历None值。
2014年

2
这比我的教授解释得更好,非常好+1。
lycuid

1
@ℕʘʘḆḽḘ,我对文本进行了一些编辑以使其更加清晰。现在它说明了中间的示例将立即将整个文件数据存储到内存中,如果文件大于当前可用的内存数量,则可能会出现问题。然后,我在第三个示例中展示如何逐行处理,以免一次消耗整个文件。
jdi

1
@ℕʘʘḆḽḘ阅读有关pool.Map()的文档。它说它将把可迭代对象分成几部分,然后提交给工人。因此最终将消耗所有行到内存中。是的,一次迭代一行可以提高内存效率,但是如果最终将所有这些行都保留在内存中,那么您将返回读取整个文件。
jdi

9

这是我制作的一个非常愚蠢的示例:

import os.path
import multiprocessing

def newlinebefore(f,n):
    f.seek(n)
    c=f.read(1)
    while c!='\n' and n > 0:
        n-=1
        f.seek(n)
        c=f.read(1)

    f.seek(n)
    return n

filename='gpdata.dat'  #your filename goes here.
fsize=os.path.getsize(filename) #size of file (in bytes)

#break the file into 20 chunks for processing.
nchunks=20
initial_chunks=range(1,fsize,fsize/nchunks)

#You could also do something like:
#initial_chunks=range(1,fsize,max_chunk_size_in_bytes) #this should work too.


with open(filename,'r') as f:
    start_byte=sorted(set([newlinebefore(f,i) for i in initial_chunks]))

end_byte=[i-1 for i in start_byte] [1:] + [None]

def process_piece(filename,start,end):
    with open(filename,'r') as f:
        f.seek(start+1)
        if(end is None):
            text=f.read()
        else: 
            nbytes=end-start+1
            text=f.read(nbytes)

    # process text here. createing some object to be returned
    # You could wrap text into a StringIO object if you want to be able to
    # read from it the way you would a file.

    returnobj=text
    return returnobj

def wrapper(args):
    return process_piece(*args)

filename_repeated=[filename]*len(start_byte)
args=zip(filename_repeated,start_byte,end_byte)

pool=multiprocessing.Pool(4)
result=pool.map(wrapper,args)

#Now take your results and write them to the database.
print "".join(result)  #I just print it to make sure I get my file back ...

这里最棘手的部分是确保我们将文件分割为换行符,以免丢失任何行(或仅读取部分行)。然后,每个进程读取文件的一部分,并返回一个可由主线程放入数据库的对象。当然,您甚至可能需要分块执行此部分,这样就不必一次将所有信息都保留在内存中。(这很容易实现-只需将“ args”列表分成X个块并调用pool.map(wrapper,chunk) -参见此处


-5

最好将一个大文件分成多个较小的文件,并在单独的线程中进行处理。


这不是OP想要的!但是只是一个主意...不错。
DRPK '17
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.