Multiprocessing BaseManager列表填充疑问:append失效但+=可行
问题
尝试通过Python Multiprocessing模块将自定义类实例传递给多个子进程,编写了如下代码:
import queue from multiprocessing import Process, Queue, Manager from multiprocessing.managers import BaseManager, NamespaceProxy class TestManager(BaseManager): pass class Test: def __init__(self): self.a = [] self.b = [] self.c = [] class TestProxy(NamespaceProxy): # We need to expose the same __dunder__ methods as NamespaceProxy, # in addition to the b method. _exposed_ = ('__getattribute__', '__setattr__', '__delattr__') def popManager(queue, manager, i): manager.a.append(i) manager.b.append(i+1) manager.c.append(i**2) queue.put(True) def managerParent(): processes = [] queues = [] TestManager.register("Test", Test, TestProxy) with TestManager() as manager: t = manager.Test() for i in range(10): queues.append(Queue()) processes.append( Process(target=popManager, args=(queues[i], t, i)) ) [p.start() for p in processes] print("started!") while True: try: [q.get(timeout=0.2) for i, q in enumerate(queues)] break except queue.Empty: print(f"Processes still running!") print(t.a, t.a, t.c) if __name__ == "__main__": managerParent()
代码可正常运行,但类内的列表实例始终为空,输出[] [] [],预期输出应为[0, 1, 2, ... 9], [1, 2, 3, ..., 10], [0, 1, 4, ..., 81]。
编辑补充:若将popManager函数修改为使用+=操作符:
def popManager(queue, manager, i): manager.a += [i] manager.b += [i+1] manager.c += [i**2] queue.put(True)
则能得到预期结果。请问为何append方法无法生效?
原因分析
这是NamespaceProxy代理对象的特性导致的:
- 调用
manager.a.append(i)时,manager.a会通过__getattribute__获取到本地副本的列表,append操作仅修改这个子进程内的本地副本,不会同步回Manager管理的远程对象,所以父进程看不到变化。 - 而
manager.a += [i]本质是manager.a = manager.a + [i]:先获取远程列表的当前值,计算出新列表后,通过__setattr__把新列表赋值给manager.a。__setattr__是你在_exposed_中声明的方法,会触发代理的同步逻辑,将修改传递回远程实例,因此父进程能看到更新后的结果。
解决办法
如果想让append生效,有两种可行方案:
方案1:封装列表操作方法并暴露给代理
在自定义类中封装列表的append操作,然后把这些方法加入代理的_exposed_列表,让代理直接调用远程对象的方法:
class Test: def __init__(self): self.a = [] self.b = [] self.c = [] def append_a(self, val): self.a.append(val) def append_b(self, val): self.b.append(val) def append_c(self, val): self.c.append(val) class TestProxy(NamespaceProxy): _exposed_ = ('__getattribute__', '__setattr__', '__delattr__', 'append_a', 'append_b', 'append_c') # 子进程中调用封装的方法 def popManager(queue, manager, i): manager.append_a(i) manager.append_b(i+1) manager.append_c(i**2) queue.put(True)
方案2:使用Manager提供的可同步列表
直接用multiprocessing.Manager创建的list()替换Test类中的普通列表,这类列表本身支持跨进程同步:
class Test: def __init__(self, manager): self.a = manager.list() self.b = manager.list() self.c = manager.list() # 在managerParent中创建Test实例时传入manager def managerParent(): processes = [] queues = [] TestManager.register("Test", Test, TestProxy) with TestManager() as manager: t = manager.Test(manager) # 后续代码不变
内容的提问来源于stack exchange,提问作者Adam
相关产品推荐
相关产品推荐

