Python current.futures多次导入库(多次在顶级范围内执行代码)



对于以下脚本(Python 3.6,Windows Anaconda(,我注意到导入的库与调用的处理器数量一样多。并且print('Hello')也会执行多次相同的次数。

我以为处理器只会被调用func1而不是整个程序。实际func1是一个繁重的 CPU 限制任务,将执行数百万次。

这是此类任务的正确框架选择吗?

import pandas as pd
import numpy as np
from concurrent.futures import ProcessPoolExecutor
print("Hello")
def func1(x):
return x

if __name__ == '__main__':
print(datetime.datetime.now())    
print('test start')
with ProcessPoolExecutor() as executor:
results = executor.map(func1, np.arange(1,1000))
for r in results:
print(r)
print('test end')
print(datetime.datetime.now())

concurrent.futures.ProcessPoolExecutor使用multiprocessing模块进行多处理。

而且,正如编程指南中所述,这意味着您必须保护不想在__main__块的每个进程中运行的任何顶级代码:

确保主模块可以由新的 Python 解释器安全地导入,而不会造成意外的副作用(例如启动新进程(。

。应该通过使用if __name__ == '__main__':来保护程序的"入口点"......

请注意,仅当使用spawnforkserverstart 方法时,才需要执行此操作。但是,如果您使用的是Windows,则默认为spawn。而且,无论如何,这样做永远不会有什么坏处,并且通常会使代码更清晰,因此无论如何都值得这样做。

您可能不想以这种方式保护您的import。毕竟,每个内核调用一次import pandas as pd的成本似乎不小,但这只发生在启动时,运行繁重的 CPU 密集型函数数百万次的成本将完全淹没它。(如果没有,您可能一开始就不想使用多处理...通常,您的defclass语句也是如此(特别是如果它们没有捕获任何闭包变量或任何东西(。只有不正确的安装程序代码才能多次运行(如示例中的print('hello')(,需要保护。


concurrent.futures文档(以及 PEP 3148(中的示例都使用"main 函数"习惯用法来处理此问题:

def main():
# all of your top-level code goes here
if __name__ == '__main__':
main()

这还有一个额外的好处,可以将您的顶级全局变量转换为局部变量,以确保您不会意外共享它们(这尤其是multiprocessing的问题,它们实际上与fork共享,但与spawn一起复制,因此相同的代码在一个平台上测试时可能有效,但在另一个平台上部署时会失败(。


如果你想知道为什么会这样:

使用forkstart 方法,multiprocessing通过克隆父 Python 解释器来创建每个新的子进程,然后仅在您(或concurrent.futures(创建池的位置启动池服务函数。因此,顶级代码不会重新运行。

使用spawnstart 方法,multiprocessing通过启动干净的新 Python 解释器、import代码,然后启动池服务函数来创建每个新的子进程。因此,顶级代码作为import的一部分重新运行。

最新更新