Я использую 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)
вместо этого?
У вашего кода, похоже, больше проблем, чем у формы. Вы получаете желаемый результат только с удачей. Повторное выполнение даст разные результаты. Это потому, что +=
не является атомной операцией. Несколько процессов могут считывать одно и то же старое значение один за другим, прежде чем какой-либо из них обновит его, и они будут записывать те же значения. Чтобы предотвратить такое поведение, вам придется использовать 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)