无法使用multiprocessing manager.dict()共享对象实例

2 投票
2 回答
5424 浏览
提问于 2025-04-27 13:53

我在用 multiprocessing 共享一个对象实例的字典时遇到了问题。我想用一个由 manager 共享的 dict,但是当我试图用对象实例作为键时,它被复制了。

import multiprocessing

class Dog():
    def __init__(self, name = "joe"):
        self.name = name
    def bark(self):
        print("woof")

mg = multiprocessing.Manager()
dt = mg.dict()
dt["a"] = 1
dt["b"] = 2
# As expected
print(dt.items()) # => [('a', 1), ('b', 2)]
dt = mg.dict()
lab = Dog("carl")
print(lab) # => <__main__.Dog instance at 0x7f8d6bb869e0>
dt[lab] = 1
# But then I don't get the ID I expect
print(dt.items()) # => [(<__main__.Dog instance at 0x7f8d6bb86908>, 1)]

我知道解决这个问题的方法是用对象的 ID 作为键,但为什么会这样呢?用对象 ID 是解决我问题的最佳方法吗?我注意到在普通的非 managerdict() 对象中并不会发生这种情况。

另一种方法

Manager() 的文档中,我读到部分问题在于如何通知服务器变化,所以我把代码改成了这样,但我仍然遇到同样的问题,我的狗被复制了,而不是引用。

import multiprocessing

class Dog():
    def __init__(self, name = "joe"):
        self.name = name
    def bark(self):
        print("woof")

mg = multiprocessing.Manager()
dt = dict()
lp = mg.list()
lp.append(dt)
print(lp)
dt["a"] = 1
dt["b"] = 2
lp[0] = dt
print(lp)
dt = dict()
lab = Dog("carl")
print(lab)
pup = Dog("steve")
print(pup)
dt[lab] = 1
dt[pup] = 2
lp[0] = dt
# Their ids change again
print(lp) 
暂无标签

2 个回答

2

根据关于管理器的文档所说:

如果你在字典或列表的代理中修改了可变值或项目,这些修改不会通过管理器传播,因为代理无法知道它的值或项目何时被修改。要修改这样的项目,你需要把修改后的对象重新赋值给容器代理。

虽然multiprocessing让多个进程之间的通信变得简单,但它仍然无法做操作系统不允许的事情(比如访问另一个进程的任意内存)。实际上,Manager是对对象的副本进行操作,这些副本在需要时会被序列化。

我明白解决这个问题的方法是使用对象ID作为键。

请注意,你无法在其他进程中获取那些对象实例。正确的方法是在你修改对象时重新赋值。

3

当你创建一个 multiprocessing.Manager 的时候,会启动一个单独的服务器进程,这个进程负责管理所有由 Manager 创建的对象。所以,如果你想把你的 Dog 实例存储到 Managerdict 中,这个实例需要被“打包”(也就是序列化)并发送到 Manager 进程。这样做的结果是,在 Manager 进程中会创建一个全新的 Dog 实例,因此它的ID和你在父进程中的 Dog 实例的ID是不同的。除了在 Manager 中也创建 Dog 实例作为 Proxy 实例外,没有其他办法可以避免这个问题:

import multiprocessing
from multiprocessing.managers import SyncManager


def Manager():
    m = SyncManager()
    m.start()
    return m

class Dog():
    def __init__(self, name = "joe"):
        self.name = name
    def bark(self):
        print("woof")

SyncManager.register("Dog", Dog)

mg = Manager()
dt = dict()
lp = mg.list()
lp.append(dt)
print(lp)
dt["a"] = 1 
dt["b"] = 2 
lp[0] = dt
print(lp)
dt = dict()
lab = mg.Dog("carl")
print(lab)
pup = mg.Dog("steve")
print(pup)
dt[lab] = 1 
dt[pup] = 2 
lp[0] = dt
# Their ids don't change
print(lp) 

输出:

<__main__.Dog instance at 0x1780098>
<__main__.Dog instance at 0x177efc8>
[{<__main__.Dog instance at 0x1780098>: 1, <__main__.Dog instance at 0x177efc8>: 2}]

请记住,这样做会让你在父进程中访问 Dog 实例的速度变慢,因为现在需要通过进程间通信(IPC)来访问 Manager 进程。

撰写回答