You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 01:32:02