Python многопроцессорность и менеджер

1

Я использую multiprocessing Python для создания параллельного приложения. Процессам необходимо поделиться некоторыми данными, для которых я использую Manager. Тем не менее, у меня есть некоторые общие функции, которые необходимо вызвать процессам и которым необходимо получить доступ к данным, хранящимся в объекте Manager. Мой вопрос заключается в том, могу ли я избежать необходимости передавать экземпляр Manager для этих общих функций в качестве аргумента и скорее использовать его как глобальный. Другими словами, рассмотрим следующий код:

import multiprocessing as mp

manager = mp.Manager()
global_dict = manager.dict(a=[0])

def add():
    global_dict['a'] += [global_dict['a'][-1]+1]

def foo_parallel(var):
    add()
    print var

num_processes = 5
p = []
for i in range(num_processes):
    p.append(mp.Process(target=foo_parallel,args=(global_dict,)))

[pi.start() for pi in p]
[pi.join() for pi in p]

Это нормально работает и возвращает p=[0,1,2,3,4,5] на моей машине. Однако это "хорошая форма"? Это хороший способ сделать это, так же хорошо, как определение add(var) и вызов add(var) вместо этого?

Теги:
multithreading
parallel-processing
multiprocessing
python-multiprocessing

1 ответ

1
Лучший ответ

У вашего кода, похоже, больше проблем, чем у формы. Вы получаете желаемый результат только с удачей. Повторное выполнение даст разные результаты. Это потому, что += не является атомной операцией. Несколько процессов могут считывать одно и то же старое значение один за другим, прежде чем какой-либо из них обновит его, и они будут записывать те же значения. Чтобы предотвратить такое поведение, вам придется использовать Manager.Lock дополнительно.


К вашему первоначальному вопросу о "хорошей форме".

ИМО было бы более чистым, чтобы основная функция дочернего процесса foo_parallel, передала global_dict явно в общую функцию add(var). Это будет форма инъекции зависимостей и имеет некоторые преимущества. В вашем примере не исчерпывающе:

  • позволяет изолировать тестирование
  • повышает повторяемость кода
  • более легкая отладка (обнаружение недопустимости управляемого объекта не должно задерживаться до тех пор, пока не будет вызван add (не работает быстро)

  • меньше кода шаблона (например, блоки try-excepts на ресурсах, требующие нескольких функций)

Как примечание. Использование списков для понимания только побочных эффектов считается "запахом кода". Если вам не нужен список в качестве результата, просто используйте for-loop.

Код:

import os
from multiprocessing import Process, Manager


def add(l):
    l += [l[-1] + 1]
    return l


def foo_parallel(global_dict, lock):
    with lock:
        l = global_dict['a']
        global_dict['a'] = add(l)
        print(os.getpid(), global_dict)


if __name__ == '__main__':

    N_WORKERS = 5

    with Manager() as manager:

        lock = manager.Lock()
        global_dict = manager.dict(a=[0])

        pool = [Process(target=foo_parallel, args=(global_dict, lock))
                for _ in range(N_WORKERS)]

        for p in pool:
            p.start()

        for p in pool:
            p.join()

        print('result', global_dict)

Ещё вопросы

Сообщество Overcoder
Наверх
Меню