Python并发.futures:ProcessPoolExecutor无法工作

Avr*_*ham 4 python concurrency time subprocess concurrent.futures

我正在尝试使用 ProcessPoolExecutor 方法,但失败了。这是一个失败使用的示例(计算两个数字的大除数)。我不明白这是什么错误

def gcd(pair):
    a, b = pair
    low = min(a, b)
    for i in range(low, 0, -1):
        if a % i == 0 and b % i == 0:
            return i

numbers = [(1963309, 2265973), (2030677, 3814172),
           (1551645, 2229620), (2039045, 2020802)]
start = time()
pool = ProcessPoolExecutor(max_workers=2)
results = list(pool.map(gcd, numbers))
end = time()
print('Took %.3f seconds' % (end - start))
Run Code Online (Sandbox Code Playgroud)

BrokenProcessPool:进程池中的进程在 future 正在运行或挂起时突然终止。

Igu*_*aut 5

将您的代码更改为如下所示,它将起作用:

from time import time
from concurrent.futures import ProcessPoolExecutor
def gcd(pair):
    a, b = pair
    low = min(a, b)
    for i in range(low, 0, -1):
        if a % i == 0 and b % i == 0:
            return i

numbers = [(1963309, 2265973), (2030677, 3814172),
           (1551645, 2229620), (2039045, 2020802)]

def main():
    start = time()
    pool = ProcessPoolExecutor(max_workers=3)
    results = list(pool.map(gcd, numbers))
    end = time()
    print('Took %.3f seconds' % (end - start))


if __name__ == '__main__':
    main()
Run Code Online (Sandbox Code Playgroud)

在支持fork()此功能的系统上,这是不必要的,因为您的脚本仅导入一次,然后启动的每个进程都ProcessPoolExecutor将在全局命名空间中拥有对象的副本,例如该gcd函数。一旦它们被分叉,它们就会经历一个引导过程,开始运行它们的目标函数(在本例中是一个从进程池执行器接受作业的工作进程循环),并且它们永远不会返回到主模块中的原始代码。他们是分叉的。

相比之下,如果您使用的spawn是 Windows 和 OSX 上默认的基于 的进程,则必须为每个工作进程从头开始启动一个新进程,并且它们必须重新导入的模块。但是,如果您的模块ProcessPoolExecutor直接在模块主体中执行类似的操作,而不像 那样对其进行保护if __name__ == '__main__':,那么他们就无法在不启动新的 .module 的情况下导入您的模块ProcessPoolExecutor。因此,您遇到的这个错误本质上是在防止您创建无限进程炸弹。

文档中提到了这一点ProcessPoolExecutor

__main__模块必须可由工作子进程导入。这意味着这ProcessPoolExecutor在交互式解释器中不起作用。

__main__但他们并没有真正弄清楚为什么会这样,或者模块“可导入”意味着什么。当您用 Python 编写一个简单的脚本并运行它时python foo.py,您的脚本foo.py将加载一个名为 的模块__main__,而不是foo您获得的名为 的模块import foo。在这种情况下,它“可导入”实际上意味着可导入,而不会产生重大副作用,例如产生新进程。