我正在使用Python的multiprocessing
创建并行应用程序。流程需要共享一些数据,对此我使用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)
一样好吗?
答案 0 :(得分:1)
您的代码示例似乎比表格具有更大的问题。您只有靠运气才能获得所需的输出。重复执行将产生不同的结果。这是因为+=
不是原子操作。在任何一个进程更新之前,多个进程可以一次又一次读取同一旧值,并且它们将回写相同的值。为了防止这种行为,您必须另外使用Manager.Lock
。
对于您最初有关“良好形式”的问题。
IMO,让子进程foo_parallel
的主要功能显式传递global_dict
到泛型函数add(var)
中会更干净。那将是dependency injection的一种形式,并具有一些优点。在您的示例中,并非详尽无遗:
- 允许隔离测试
- 提高代码的可重用性
更方便的调试(检测被管理对象的不可访问性不应延迟,直到调用
add
(fail fast)更少的样板代码(例如,需要多个功能的资源上的try-excepts块)
作为旁注。仅将列表理解用于其副作用被认为是“代码异味”。如果不需要结果列表,则使用for循环。
代码:
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)