为什么以下使用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。
答案 0 :(得分:5)
concurrent.futures.ProcessPoolExecutor
和multiprocessing.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。