Python багатопроцесорний безпечний запис у файл


80

Я намагаюся вирішити велику числову проблему, яка включає безліч підпроблем, і я використовую багатопроцесорний модуль Python (зокрема Pool.map), щоб розділити різні незалежні підзадачі на різні ядра. Кожна підзадача включає обчислення безлічі підпроблем, і я намагаюся ефективно запам'ятати ці результати, зберігаючи їх у файлі, якщо вони ще не були обчислені в будь-якому процесі, інакше пропустимо обчислення і просто прочитаємо результати з файлу.

У мене проблеми з паралельністю з файлами: різні процеси іноді перевіряють, чи не обчислюється ще підпроблема (шукаючи файл, де будуть зберігатися результати), переконайтеся, що цього не було, запустіть обчислення, потім спробуйте записати результати в той самий файл одночасно. Як мені уникнути подібних зіткнень?


3
Перегляньте приклад із документації використання multiprocessing.Lockдля синхронізації декількох процесів.
— Джон Він'ярд

13
Ви можете мати лише один процес запису результатів, із чергою як вхідними даними, яку можуть подавати інші робочі процеси. Я вважаю, що було б безпечно мати всі робочі процеси лише для читання.
— GP89

Я повинен був згадати, що, щоб ускладнити ситуацію, я запускаю кілька різних великих основних проблем одночасно на кластері, причому кожна з них записує результати в підпроблеми в одній мережевій файловій системі. Таким чином, я можу отримати зіткнення з процесів, що працюють на окремих машинах (тому я не думаю, що рішення, що використовують такі речі, як багатопроцесорність. Lock будуть працювати).
— Big Dogg

2
Якщо ваша мережева файлова система підтримує блокування файлів, ви можете використовувати метод створення конкретного файлу os, щоб виключно створити файл і утримувати на ньому ексклюзивний замок, поки результати не будуть готові, а потім закрийте його. Будь-який процес, який не зміг "перемогти" у створеній гонці, спробував би її відкрити та повторити спробу (із затримкою), поки не змогли її відкрити, тоді вони зможуть прочитати результати.
— JimP

10
Ви по суті тут програмуєте сервер баз даних. Ви роздумували про використання вже існуючого?
— georg

Відповіді:


139

@ GP89 згадав хороше рішення. Використовуйте чергу, щоб надіслати завдання написання до спеціального процесу, який має єдиний доступ до запису у файл. Усі інші працівники мають доступ лише для читання. Це усуне зіткнення. Ось приклад, який використовує apply_async, але він також буде працювати з картою:

import multiprocessing as mp
import time

fn = 'c:/temp/temp.txt'

def worker(arg, q):
    '''stupidly simulates long running process'''
    start = time.clock()
    s = 'this is a test'
    txt = s
    for i in range(200000):
        txt += s 
    done = time.clock() - start
    with open(fn, 'rb') as f:
        size = len(f.read())
    res = 'Process' + str(arg), str(size), done
    q.put(res)
    return res

def listener(q):
    '''listens for messages on the q, writes to file. '''

    with open(fn, 'w') as f:
        while 1:
            m = q.get()
            if m == 'kill':
                f.write('killed')
                break
            f.write(str(m) + '\n')
            f.flush()

def main():
    #must use Manager queue here, or will not work
    manager = mp.Manager()
    q = manager.Queue()    
    pool = mp.Pool(mp.cpu_count() + 2)

    #put listener to work first
    watcher = pool.apply_async(listener, (q,))

    #fire off workers
    jobs = []
    for i in range(80):
        job = pool.apply_async(worker, (i, q))
        jobs.append(job)

    # collect results from the workers through the pool result queue
    for job in jobs: 
        job.get()

    #now we are done, kill the listener
    q.put('kill')
    pool.close()
    pool.join()

if __name__ == "__main__":
   main()

1
Гей, Майк, дякую за відповідь. Я думаю, що це спрацює для питання, як я його сформулював, але я не настільки впевнений, що це вирішить всю проблему, як зазначено в коментарях до питання, зокрема, як у мене є кілька основних програм, що працюють на декількох машинах в мережі файлова система, усі з яких можуть мати процеси, які намагатимуться записати в той самий файл. (FWIW, деякий час тому я впорався зі своєю особистою проблемою, але коментую, якщо інші мають подібні проблеми.)
— Big Dogg,

4
Я справді хотів би висловитись за це багато разів. Це було мені корисно стільки разів. Сьогодні ще раз.
— Едуардо

12
Мені довелося додати pool.join()нижче pool.close(). Інакше мої працівники закінчували б перед слухачем, і процес просто зупинявся.
— herrherr

2
Що можна сказати про те, коли споживач значно перевершує чисельність і викликає проблеми з пам’яттю? Як би ви реалізували декількох споживачів, які всі записують в один файл?
— ccdpowell

15
чому mp.cpu_count() + 2при встановленні кількості процесів?
— JenkinsY

1

Мені здається, вам потрібно використовувати Managerтимчасово збереження результатів у списку, а потім запис результатів зі списку у файл. Також використовуйте starmapдля передачі об’єкта, який ви хочете обробити, та керованого списку. Першим кроком є ​​побудова параметра, якому потрібно передати starmap, який включає керований список.

from multiprocessing import Manager
from multiprocessing import Pool  
import pandas as pd

def worker(row, param):
    # do something here and then append it to row
    x = param**2
    row.append(x)

if __name__ == '__main__':
    pool_parameter = [] # list of objects to process
    with Manager() as mgr:
        row = mgr.list([])

        # build list of parameters to send to starmap
        for param in pool_parameter:
            params.append([row,param])

        with Pool() as p:
            p.starmap(worker, params)

З цього моменту вам потрібно вирішити, як ви збираєтеся обробляти список. Якщо у вас тонни оперативної пам’яті та величезний набір даних, сміливо об’єднуйтесь за допомогою панд. Тоді ви можете дуже легко зберегти файл у форматі CSV або засолу.

        df = pd.concat(row, ignore_index=True)

        df.to_pickle('data.pickle')
        df.to_csv('data.csv')

3
Чи можу я отримати відгук про те, чому за це було проголосовано? Я бачу, що прийнята відповідь набагато краща. Я просто хочу вчитися.
— fizix137
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.