为什么并发.futures.ProcessPoolExecutor和multiprocessing.pool.Pool在Python中使用super失败?

时间:2019-06-15 11:16:29

标签: python pickle python-multiprocessing process-pool

为什么以下使用concurrent.futures模块的Python代码会永远挂起?

import concurrent.futures


class A:

    def f(self):
        print("called")


class B(A):

    def f(self):
        executor = concurrent.futures.ProcessPoolExecutor(max_workers=2)
        executor.submit(super().f)


if __name__ == "__main__":
    B().f()

该调用引发了一个不可见的异常[Errno 24] Too many open files(要查看此异常,请将行executor.submit(super().f)替换为print(executor.submit(super().f).exception()))。

但是,将ProcessPoolExecutor替换为ThreadPoolExecutor会按预期打印“被呼叫”。

为什么以下使用multiprocessing.pool模块的Python代码引发异常AssertionError: daemonic processes are not allowed to have children

import multiprocessing.pool


class A:

    def f(self):
        print("called")


class B(A):

    def f(self):
        pool = multiprocessing.pool.Pool(2)
        pool.apply(super().f)


if __name__ == "__main__":
    B().f()

但是,将Pool替换为ThreadPool会按预期打印“被呼叫”。

环境:CPython 3.7,MacOS 10.14。

1 个答案:

答案 0 :(得分:5)

concurrent.futures.ProcessPoolExecutormultiprocessing.pool.Pool使用multiprocessing.queues.Queue将工作函数对象从调用者传递到工作进程,Queue使用pickle模块进行序列化/反序列化,但是它无法正确处理带有子类实例的绑定方法对象:

f = super().f
print(f)
pf = pickle.loads(pickle.dumps(f))
print(pf)

输出:

<bound method A.f of <__main__.B object at 0x104b24da0>>
<bound method B.f of <__main__.B object at 0x104cfab38>>

A.f变成B.f,这实际上在工作进程中创建了对B.f的无限递归调用B.f

pickle.dumps利用绑定方法对象IMO的__reduce__方法,its implementation不考虑这种情况,该情况不涉及实际的func对象,但只能尝试使用简单名称(self)从实例B() obj(f)取回,结果是B.f,很可能是一个错误。

好消息是,我们知道问题出在哪里,我们可以通过实施我们自己的归约函数来解决它,该函数试图从原始函数(A.f)和实例obj({{ 1}}):

B()

我们可以这样做,因为绑定方法是一个描述符。

ps:我已经提交了a bug report