Обробка одного файлу з декількох процесів


82

У мене є один великий текстовий файл, в якому я хочу обробити кожен рядок (виконати деякі операції) і зберегти їх у базі даних. Оскільки одна проста програма займає занадто багато часу, я хочу, щоб це робилося за допомогою декількох процесів або потоків. Кожен потік / процес повинен зчитувати РІЗНІ дані (різні рядки) з цього окремого файлу та виконувати деякі операції з їх частиною даних (рядками) та поміщати їх у базу даних так, щоб врешті-решт у мене були оброблені цілі дані та база даних скидається з даними, які мені потрібні.

Але я не можу зрозуміти, як саме до цього підходити.


3
Приємне запитання. У мене теж був такий сумнів. Хоча я вирішив розбити файл на менші файли :)
Sushant Gupta

Відповіді:


109

Ви шукаєте шаблон виробника / споживача

Приклад базової різьби

Ось базовий приклад використання потокового модуля (замість багатопроцесорної обробки)

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

Ви не поділилися б об'єктом файлу з потоками. Ви могли б зробити для них роботу, надавши черзі рядки даних. Потім кожен потік підбирав рядок, обробляв його, а потім повертав у чергу.

У мультипроцесорний модуль вбудовано кілька вдосконалених засобів для обміну даними, таких як списки та особливий вид черги . Існують компроміси щодо використання багатопроцесорної обробки проти потоків, і це залежить від того, чи ваша робота пов’язана з процесором чи 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(), який буде циклічно над ним і доставляти рядки робочій функції. Карта блокує і повертає весь результат, коли це зроблено. Майте на увазі, що це надто спрощений приклад, і 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
Це добре, але що робити, якщо обробка пов’язана з операціями вводу-виводу? У цьому випадку паралелізм може сповільнити ситуацію, а не пришвидшити її. Шукання в межах однієї дискової доріжки набагато швидші, ніж пошуки міждоріжки, і паралельне введення-виведення має тенденцію вводити пошуки між доріжками, що в іншому випадку було б послідовним завантаженням вводу-виводу. Щоб отримати певну користь від паралельного вводу-виводу, іноді дуже добре допомагає використання дзеркала RAID.
user1277476

2
@ jwillis0720 - Звичайно. (None,) * num_workersстворює кортеж із значень None, рівних розміру кількості робітників. Це будуть сторожові значення, які вказують кожному потоку вийти, оскільки роботи більше немає. itertools.chainФункція дозволяє вам помістити кілька послідовностей в одну віртуальну послідовність без необхідності копіювати що - або. Отже, що ми отримуємо, це те, що спочатку він перебирає рядки у файлі, а потім значення None.
jdi

2
Це краще пояснити, ніж мій професор, дуже приємно +1.
лікуїд

1
@ ℕʘʘḆḽḘ, я трохи відредагував свій текст, щоб бути більш зрозумілим. Тепер це пояснює, що середній приклад збирається одночасно вилучити всі дані вашого файлу в пам'ять, що може бути проблемою, якщо ваш файл перевищує обсяг оперативної пам'яті, який у вас зараз є. Потім я показую в 3-му прикладі, як переходити рядок за рядком, щоб не споживати весь файл відразу.
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) - Дивіться тут )


-3

добре розбити один великий файл на кілька менших файлів і обробити кожен з них окремими потоками.


це не те, що хоче ОП !! але просто для ідеї ... непогано.
DRPK
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.