当函数是 lambda 或嵌套函数时,concurrent.futures.ProcessPoolExecutor 会挂起

zyx*_*xue 3 python multiprocessing concurrent.futures

任何人都可以深入了解为什么使用 lambda 或嵌套函数 ( f) 会concurrent.futures.ProcessPoolExecutor在以下代码示例中挂起吗?

\n
import concurrent.futures\n\xe2\x80\x8b\n\xe2\x80\x8b\ndef f2(s):\n    return len(s)\n\xe2\x80\x8b\n\xe2\x80\x8b\ndef main():\n    def f(s):\n        return len(s)\n\xe2\x80\x8b\n    data = ["a", "b", "c"]\n\xe2\x80\x8b\n    with concurrent.futures.ProcessPoolExecutor(max_workers=1) as pool:\n        # results = pool.map(f, data) # hangs\n        # results = pool.map(lambda d: len(d), data)  # hangs\n        # results = pool.map(len, data)  # works\n        results = pool.map(f2, data) # works\n\xe2\x80\x8b\n    print(list(results))\n\xe2\x80\x8b\n\xe2\x80\x8b\nif __name__ == "__main__":\n    main()\n
Run Code Online (Sandbox Code Playgroud)\n

Dr.*_*gex 6

长话短说,Pool/ProcessPoolExecutor 都必须序列化所有内容,然后再将其发送给工作人员。序列化(有时也称为 pickling)实际上是保存函数名称的过程,只有在 Pool 想要访问它时才再次导入。为了使此过程正常工作,必须在顶层定义该函数,因为子级无法导入嵌套函数,这就是出现以下错误的原因:

AttributeError: Can't pickle local object 'MyClass.mymethod.<locals>.mymethod'
Run Code Online (Sandbox Code Playgroud)

为了避免这个问题,有一些我认为不可靠的解决方案。如果您可以灵活地使用其他软件包,pathos是一个真正有效的替代方案。例如,以下内容不会挂起:

import pathos
import os

class SomeClass:

    def __init__(self):
         self.words = ["a", "b", "c"]

    def some_method(self):
    
        def run(s):
            return len(s)
    
        return list(pool.map(run, self.words))

pool = pathos.multiprocessing.Pool(os.cpu_count())
print(SomeClass().some_method())
Run Code Online (Sandbox Code Playgroud)

它确实会打印

[1, 1, 1]
Run Code Online (Sandbox Code Playgroud)