Multiprocessing Manager在使用pool.apply_async的非常简单的示例上失败

问题描述

我在代码中发现了与python multiprocessing有关的某些意外行为,尤其是Manager类。我写了一个超级简单的示例,试图更好地理解正在发生的事情:

import multiprocessing as mp
from collections import defaultdict


def process(d):
    print('doing the process')
    d['a'] = []
    d['a'].append(1)
    d['a'].append(2)


def main():
    pool = mp.Pool(mp.cpu_count())
    with mp.Manager() as manager:
        d = manager.dict({'c': 2})
        result = pool.apply_async(process,args=(d))
        print(result.get())

        pool.close()
        pool.join()

        print(d)


if __name__ == '__main__':
    main()

此操作失败,并且从result.get()打印的堆栈跟踪如下:

multiprocessing.pool.RemoteTraceback:
"""
Traceback (most recent call last):
  File "/usr/local/Cellar/python/3.7.5/Frameworks/Python.framework/Versions/3.7/lib/python3.7/multiprocessing/pool.py",line 121,in worker
    result = (True,func(*args,**kwds))
  File "<string>",line 2,in __iter__
  File "/usr/local/Cellar/python/3.7.5/Frameworks/Python.framework/Versions/3.7/lib/python3.7/multiprocessing/managers.py",line 825,in _callmethod
    proxytype = self._manager._registry[token.typeid][-1]
AttributeError: 'nonetype' object has no attribute '_registry'
"""

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "mp_test.py",line 34,in <module>
    main()
  File "mp_test.py",line 25,in main
    print(result.get())
  File "/usr/local/Cellar/python/3.7.5/Frameworks/Python.framework/Versions/3.7/lib/python3.7/multiprocessing/pool.py",line 657,in get
    raise self._value
AttributeError: 'nonetype' object has no attribute '_registry'

我仍然不清楚这里发生了什么。在我看来,这是Manager类的非常非常简单的应用程序。它几乎是实际example used in the official python documentation的副本,唯一的区别是我正在使用池并使用apply_async运行该过程。我这样做是因为这是我在实际项目中使用的。

澄清一下,如果我没有result = print(result.get())的话,就不会得到堆栈跟踪。运行脚本时,我只看到{'c': 2}打印出来,这向我表明出了点问题,没有显示

解决方法

首先要注意的两件事:首先,这不是您运行的代码。您发布的代码有

  result = pool.apply_async(process2,args=(d))

,但未定义process2()。假设打算使用“ process”,那么下一个是

args=(d)

部分。就像输入

args=d

但这不是所需。您需要传递一系列预期的参数。因此,您需要将该部分更改为

args=(d,) # build a 1-tuple

args=[d]  # build a list

然后输出更改为

{'c': 2,'a': []}

为什么“ a”列表中没有1和2?因为只有字典本身才存在于管理器服务器上。

d['a'].append(1)

首先从服务器获取“ a”的映射,这是一个空列表。但是该空列表不会以任何方式共享-它在process()中是本地的。您将其附加1,然后将其丢弃-服务器对此一无所知。 2也一样。

要获得所需的内容,您需要“执行某些操作”以将更改内容告知管理服务器。例如,

d['a'] = L = []
L.append(1)
L.append(2)
d['a'] = L