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

Mag*_*ero 5 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()
Run Code Online (Sandbox Code Playgroud)

这个调用产生了一个无形的例外[Errno 24] Too many open files(见它,更换线路executor.submit(super().f)print(executor.submit(super().f).exception()))。

但是,按预期将打印替换ProcessPoolExecutorThreadPoolExecutor“被调用”。

为什么以下使用该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()
Run Code Online (Sandbox Code Playgroud)

但是,按预期将打印替换PoolThreadPool“被调用”。

环境:CPython 3.7,MacOS 10.14。

geo*_*xsh 6

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

f = super().f
print(f)
pf = pickle.loads(pickle.dumps(f))
print(pf)
Run Code Online (Sandbox Code Playgroud)

输出:

<bound method A.f of <__main__.B object at 0x104b24da0>>
<bound method B.f of <__main__.B object at 0x104cfab38>>
Run Code Online (Sandbox Code Playgroud)

A.f变成B.f,这有效B.fB.f在worker进程中创建了无限递归调用。

pickle.dumps利用__reduce__IMO方法的绑定方法对象IMO 的实现,不考虑这种情况,这种情况不涉及实际func对象,而只是尝试从具有简单名称()的实例selfobj(B())取回f,结果是B.f,很可能是错误。

好消息是,我们知道问题出在哪里,我们可以通过实现我们自己的归约函数来解决它,该函数尝试从原始函数(A.f)和实例obj(B())重新创建绑定的方法对象:

import types
import copyreg
import multiprocessing

def my_reduce(obj):
    return (obj.__func__.__get__, (obj.__self__,))

copyreg.pickle(types.MethodType, my_reduce)
multiprocessing.reduction.register(types.MethodType, my_reduce)
Run Code Online (Sandbox Code Playgroud)

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

ps:我已经提交了错误报告