对于以下脚本(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__':
来保护程序的"入口点"......
请注意,仅当使用spawn
或forkserver
start 方法时,才需要执行此操作。但是,如果您使用的是Windows,则默认为spawn
。而且,无论如何,这样做永远不会有什么坏处,并且通常会使代码更清晰,因此无论如何都值得这样做。
您可能不想以这种方式保护您的import
。毕竟,每个内核调用一次import pandas as pd
的成本似乎不小,但这只发生在启动时,运行繁重的 CPU 密集型函数数百万次的成本将完全淹没它。(如果没有,您可能一开始就不想使用多处理...通常,您的def
和class
语句也是如此(特别是如果它们没有捕获任何闭包变量或任何东西(。只有不正确的安装程序代码才能多次运行(如示例中的print('hello')
(,需要保护。
concurrent.futures
文档(以及 PEP 3148(中的示例都使用"main 函数"习惯用法来处理此问题:
def main():
# all of your top-level code goes here
if __name__ == '__main__':
main()
这还有一个额外的好处,可以将您的顶级全局变量转换为局部变量,以确保您不会意外共享它们(这尤其是multiprocessing
的问题,它们实际上与fork
共享,但与spawn
一起复制,因此相同的代码在一个平台上测试时可能有效,但在另一个平台上部署时会失败(。
如果你想知道为什么会这样:
使用fork
start 方法,multiprocessing
通过克隆父 Python 解释器来创建每个新的子进程,然后仅在您(或concurrent.futures
(创建池的位置启动池服务函数。因此,顶级代码不会重新运行。
使用spawn
start 方法,multiprocessing
通过启动干净的新 Python 解释器、import
代码,然后启动池服务函数来创建每个新的子进程。因此,顶级代码作为import
的一部分重新运行。