首页 > 解决方案 > 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())

标签: pythoncpupython-multiprocessingpython-multithreadingconcurrent.futures

解决方案


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

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

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

...应该通过使用if __name__ == '__main__':...来保护程序的“入口点”</p>

spawn请注意,只有在使用or forkserver start 方法时才需要这样做。但如果您使用的是 Windows,spawn则为默认设置。而且,无论如何,这样做永远不会有坏处,而且通常会使代码更清晰,所以无论如何都值得这样做。

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


文档中的示例concurrent.futures(以及PEP 3148中的示例)都使用“main function”习语来处理这个问题:

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.


推荐阅读