Xử lý một tệp từ nhiều quy trình


82

Tôi có một tệp văn bản lớn, trong đó tôi muốn xử lý từng dòng (thực hiện một số thao tác) và lưu trữ chúng trong cơ sở dữ liệu. Vì một chương trình đơn giản mất quá nhiều thời gian, tôi muốn nó được thực hiện thông qua nhiều quy trình hoặc chuỗi. Mỗi luồng / quy trình nên đọc dữ liệu KHÁC NHAU (các dòng khác nhau) từ tệp đơn đó và thực hiện một số thao tác trên phần dữ liệu (dòng) của chúng và đưa chúng vào cơ sở dữ liệu để cuối cùng, tôi có toàn bộ dữ liệu được xử lý và cơ sở dữ liệu được kết xuất với dữ liệu tôi cần.

Nhưng tôi không thể tìm ra cách tiếp cận điều này.


3
Câu hỏi hay. Tôi cũng có nghi ngờ này. Mặc dù tôi đã đi với các tùy chọn chia tệp thành các tệp nhỏ hơn :)
Sushant Gupta

Câu trả lời:


109

Những gì bạn đang tìm kiếm là mô hình Nhà sản xuất / Người tiêu dùng

Ví dụ về luồng cơ bản

Đây là một ví dụ cơ bản sử dụng mô-đun phân luồng (thay vì đa xử lý)

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()

Bạn sẽ không chia sẻ đối tượng tệp với các chủ đề. Bạn sẽ tạo ra công việc cho họ bằng cách cung cấp hàng đợi với các dòng dữ liệu. Sau đó, mỗi luồng sẽ chọn một dòng, xử lý nó và sau đó trả lại nó trong hàng đợi.

Có một số tiện ích nâng cao hơn được tích hợp trong mô-đun đa xử lý để chia sẻ dữ liệu, như danh sách và loại Hàng đợi đặc biệt . Có sự đánh đổi khi sử dụng đa xử lý so với luồng và điều đó phụ thuộc vào việc công việc của bạn bị ràng buộc cpu hay IO.

Đa xử lý cơ bản. Ví dụ về công cụ

Đây là một ví dụ thực sự cơ bản về Nhóm đa xử lý

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 là một đối tượng tiện lợi quản lý các quy trình của riêng nó. Vì một tệp đang mở có thể lặp qua các dòng của nó, bạn có thể chuyển nó đến tệp, tệp pool.map()này sẽ lặp qua nó và cung cấp các dòng cho hàm worker. Bản đồ chặn và trả về toàn bộ kết quả khi hoàn thành. Hãy lưu ý rằng đây là một ví dụ quá đơn giản và pool.map()sẽ đọc toàn bộ tệp của bạn vào bộ nhớ cùng một lúc trước khi xử lý công việc. Nếu bạn muốn có các tệp lớn, hãy ghi nhớ điều này. Có nhiều cách nâng cao hơn để thiết kế một nhà sản xuất / người tiêu dùng.

"Nhóm" thủ công với giới hạn và sắp xếp lại dòng

Đây là một ví dụ thủ công của Pool.map , nhưng thay vì sử dụng toàn bộ tệp có thể lặp lại trong một lần, bạn có thể đặt kích thước hàng đợi để bạn chỉ nạp từng phần một nhanh nhất có thể. Tôi cũng đã thêm số dòng để bạn có thể theo dõi chúng và tham khảo chúng nếu bạn muốn, sau này.

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ều này là tốt, nhưng nếu quá trình xử lý bị ràng buộc I / O thì sao? Trong trường hợp đó, song song có thể làm chậm mọi thứ hơn là tăng tốc độ. Các tìm kiếm trong một rãnh đĩa đơn lẻ nhanh hơn nhiều so với tìm kiếm giữa các rãnh và thực hiện I / O song song có xu hướng giới thiệu các tìm kiếm giữa các rãnh trong những gì sẽ là tải I / O tuần tự. Để có được một số lợi ích từ I / O song song, đôi khi việc sử dụng máy nhân bản RAID sẽ hữu ích một chút.
dùng1277476

2
@ jwillis0720 - Chắc chắn rồi. (None,) * num_workerstạo một bộ giá trị Không có bằng kích thước của số lượng công nhân. Đây sẽ là các giá trị sentinel yêu cầu mỗi luồng thoát ra vì không còn công việc nữa. Các itertools.chainchức năng cho phép của bạn đặt nhiều chuỗi lại với nhau thành một chuỗi ảo mà không cần phải sao chép bất cứ điều gì. Vì vậy, những gì chúng ta nhận được là đầu tiên nó lặp lại trên các dòng trong tệp, sau đó là các giá trị Không có.
JDI

2
Điều đó được giải thích tốt hơn giáo sư của tôi, +1 rất hay.
lycuid

1
@ ℕʘʘḆḽḘ, tôi đã chỉnh sửa văn bản của mình một chút để rõ ràng hơn. Bây giờ nó giải thích rằng ví dụ giữa sẽ chuyển toàn bộ dữ liệu tệp của bạn vào bộ nhớ cùng một lúc, điều này có thể là một vấn đề nếu tệp của bạn lớn hơn dung lượng ram bạn hiện có. Sau đó, tôi chỉ ra trong ví dụ thứ 3 cách đi từng dòng một, để không sử dụng toàn bộ tệp cùng một lúc.
jdi

1
@ ℕʘʘḆḽḘ đọc tài liệu cho pool.Map (). Nó nói rằng nó sẽ chia những thứ có thể lặp lại thành nhiều phần và gửi chúng cho công nhân. Vì vậy, nó sẽ tiêu thụ tất cả các dòng vào bộ nhớ. Có, lặp đi lặp lại từng dòng một là hiệu quả về bộ nhớ, nhưng nếu bạn kết thúc việc giữ tất cả các dòng đó trong bộ nhớ thì bạn sẽ quay lại đọc toàn bộ tệp.
jdi

9

Đây là một ví dụ thực sự ngu ngốc mà tôi đã đúc kết ra:

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 ...

Phần khó ở đây là đảm bảo rằng chúng tôi chia tệp trên các ký tự dòng mới để bạn không bỏ lỡ bất kỳ dòng nào (hoặc chỉ đọc một phần các dòng). Sau đó, mỗi tiến trình đọc nó là một phần của tệp và trả về một đối tượng có thể được luồng chính đưa vào cơ sở dữ liệu. Tất nhiên, bạn thậm chí có thể cần thực hiện phần này theo từng phần để không phải lưu giữ tất cả thông tin trong bộ nhớ cùng một lúc. (điều này khá dễ thực hiện - chỉ cần chia danh sách "args" thành các phần X và gọi pool.map(wrapper,chunk) - Xem tại đây )


-4

chia nhỏ một tệp lớn thành nhiều tệp nhỏ hơn và xử lý từng tệp trong các chuỗi riêng biệt.


đây không phải là điều mà OP muốn !! nhưng chỉ cho một ý tưởng ... không tồi.
DRPK
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.