我写了这样的程序:
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
我想在进程之间创建一个共享列表列表,每个进程都在其中修改一个列表。但是该程序的结果是一个空列表的列表:[[],[],[],[],[]]。
怎么了
我认为这是由于实施经理的方式有些古怪。
如果创建两个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]
z
>>> l[0].append("hi") >>> print(z) [] >>> z.append("hi again") >>> print(l[0]) ['hi again']
如您所见,更改嵌套列表对ListProxy对象没有任何影响,但是更改ListProxy对象确实会更改嵌套列表。该文档实际上明确指出了这一点:
注意 不能通过管理器传播对dict和list代理中的可变值或项的修改,因为代理无法知道何时修改其值或项。要修改此类项目,可以将修改后的对象重新分配给容器代理:
注意
不能通过管理器传播对dict和list代理中的可变值或项的修改,因为代理无法知道何时修改其值或项。要修改此类项目,可以将修改后的对象重新分配给容器代理:
append深入研究源代码,您可以看到,当您调用ListProxy时,附加调用实际上是通过IPC发送给管理器对象的,然后管理器调用了在共享列表上的附加。这意味着append需要腌制/未腌制的args 。在取消提取过程中,ListProxy对象被转换为常规的Python列表,该列表是ListProxy指向的内容(也称为其引用对象)的副本。文档中也对此进行了说明:
append
代理对象的一个重要功能是可拾取的,因此可以在进程之间传递。但是请注意,如果将代理发送给相应管理者的进程,则取消选择代理将自己生成引用对象。例如,这意味着一个共享对象可以包含第二个
因此,回到上面的示例,如果l [0]是的副本z,为什么更新z也会更新l[0]?由于副本也已使用Proxy对象注册,因此,当您更改ListProxy(z在上面的示例中)时,它还会更新列表的所有已注册副本(l[0]在上面的示例中)。但是,副本对代理一无所知,因此当您更改副本时,代理不会更改。
因此,为了使您的示例正常工作,您manager.list()每次需要修改子列表时都需要创建一个新对象,并且仅直接更新该代理对象,而不是通过父列表的索引进行更新:
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]]