我写了一个这样的程序:
from multiprocessing import Process, Manager
def worker(i):
x[i].append(i)
if __name__ == '__main__':
manager = Manager()
x = manager.list()
for i in range(5):
x.append([])
p = []
for i in range(5):
p.append(Process(target=worker, args=(i,)))
p[i].start()
for i in range(5):
p[i].join()
print x
我想在进程之间创建一个列表的共享列表,每个进程都会在其中修改一个列表。但是这个程序的结果是一个空列表列表:[[],[],[],[],[]]。
出了什么问题?
答案 0 :(得分:10)
我认为这是因为管理者实施方式的怪癖。
如果您创建了两个Manager.list对象,然后将其中一个列表附加到另一个列表,则您追加的列表类型会在父列表中更改:
>>> type(l)
<class 'multiprocessing.managers.ListProxy'>
>>> type(z)
<class 'multiprocessing.managers.ListProxy'>
>>> l.append(z)
>>> type(l[0])
<class 'list'> # Not a ListProxy anymore
l[0]
和z
不是同一个对象,并且表现得不像您期望的那样:
>>> l[0].append("hi")
>>> print(z)
[]
>>> z.append("hi again")
>>> print(l[0])
['hi again']
如您所见,更改嵌套列表对ListProxy对象没有任何影响,但更改ListProxy对象确实会更改嵌套列表。文档实际上是explicitly notes this:
请注意
对dict和列表代理中的可变值或项的修改将会 不能通过管理器传播,因为代理无法通过 知道何时修改其值或项目。要修改这样的项目, 您可以将修改后的对象重新分配给容器代理:
通过源代码挖掘,您可以看到当您在ListProxy上调用append
时,追加调用实际上是通过IPC发送到管理器对象,然后管理器调用附加在共享列表上。这意味着append
的args需要进行pickle / unpickled。在unpickling过程中,ListProxy对象变成一个常规的Python列表,它是ListProxy指向的副本(也就是它的指示对象)。这也是noted in the documentation:
代理对象的一个重要特征是它们是可选择的 它们可以在进程之间传递。但请注意,如果是代理 被发送到相应的经理的过程然后取消它将 产生指示物本身。这意味着,例如,一个共享对象可以包含第二个
所以,回到上面的例子,如果l [0]是z
的副本,为什么更新z
也会更新l[0]
?因为副本也会在Proxy对象中注册,因此,当您更改ListProxy(上例中的z
)时,它还会更新列表中的所有已注册副本(l[0]
在示例中以上)。但是,副本对代理一无所知,因此当您更改副本时,代理不会更改。
因此,为了使您的示例正常工作,您需要在每次要修改子列表时创建一个新的manager.list()
对象,并且只需直接更新该代理对象,而不是通过索引更新它父列表:
#!/usr/bin/python
from multiprocessing import Process, Manager
def worker(x, i, *args):
sub_l = manager.list(x[i])
sub_l.append(i)
x[i] = sub_l
if __name__ == '__main__':
manager = Manager()
x = manager.list([[]]*5)
print x
p = []
for i in range(5):
p.append(Process(target=worker, args=(x, i)))
p[i].start()
for i in range(5):
p[i].join()
print x
这是输出:
dan@dantop2:~$ ./multi_weirdness.py
[[0], [1], [2], [3], [4]]