5

1.为什么下面使用该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())

但是,按预期替换ProcessPoolExecutorThreadPoolExecutor“称为”的打印件。

2.为什么下面使用该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()

但是,按预期替换PoolThreadPool“称为”的打印件。

环境:CPython 3.7,MacOS 10.14。

4

1 回答 1

6

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.fB.f

pickle.dumps利用__reduce__绑定方法对象的方法,IMO,它的实现,没有考虑这种情况,它不关心真实的对象,而只是尝试用简单的名称( )从实例obj( )func中取回,结果,很可能是一个错误。selfB()fB.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)

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

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

于 2019-06-15T22:50:12.553 回答