Python current.futures импортирует библиотеки несколько раз (многократно выполняет код в верхней области) - PullRequest
0 голосов
/ 13 июня 2018

для следующего скрипта (python 3.6, windows anaconda) я заметил, что библиотеки импортируются столько раз, сколько было вызвано процессоров.И print('Hello') также выполняются несколько раз.

Я думал, что процессоры будут вызываться только для вызова func1, а не для всей программы.Фактическая func1 - это сложная задача, связанная с процессором, которая будет выполняться миллионы раз.

Правильный ли это выбор фреймворка для такой задачи?

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())

1 Ответ

0 голосов
/ 13 июня 2018

concurrent.futures.ProcessPoolExecutor использует модуль multiprocessing для своей многопроцессорной обработки.

И, как объясняется в Руководства по программированию , это означает, что вы должны защититьлюбой код верхнего уровня, который вы не хотите запускать в каждом процессе в вашем __main__ блоке:

Убедитесь, что основной модуль может быть безопасно импортирован новым интерпретатором Python, не вызывая непреднамеренную сторонуэффекты (такие как запуск нового процесса).

... нужно защищать «точку входа» программы, используя if __name__ == '__main__':

Обратите внимание, что это тольконеобходимо, если используются spawn или forkserver методы запуска .Но если вы работаете в Windows, spawn по умолчанию.И, во всяком случае, это никогда не делает больно делать это, и обычно делает код более понятным, так что в любом случае это стоит делать.

Вы, вероятно, не хотитечтобы защитить ваши import с этим способом.В конце концов, стоимость вызова import pandas as pd один раз для каждого ядра может показаться нетривиальной, но это происходит только при запуске, а стоимость запуска тяжелой функции, связанной с процессором, в миллионы раз полностью ее затопит.(Если нет, то вы, вероятно, не хотели использовать многопроцессорность в первую очередь…) И обычно то же самое относится к вашим операторам def и class (особенно, если они не захватывают какие-либо переменные замыкания или что-либо еще).Только код установки, который некорректно запускаться несколько раз (например, print('hello') в вашем примере), должен быть защищен.


Примеры в concurrent.futures документе (и в PEP3148 ) все справляются с этим с помощью идиомы "main function":

def main():
    # all of your top-level code goes here

if __name__ == '__main__':
    main()

Это дает дополнительное преимущество превращения ваших глобальных глобалов верхнего уровня в локальных, чтобы гарантировать, что вы случайно не поделитесьих (что может быть особенно проблематично с multiprocessing, когда они на самом деле делятся с fork, но копируются с spawn, поэтому один и тот же код может работать при тестировании на одной платформе, но может не работать при развертывании на другой).


Если вы хотите узнать , почему это происходит:

С методом запуска fork, multiprocessing создает каждый новый дочерний процесс путем клонированияродительский интерпретатор Python, а затем просто запустить функцию обслуживания пула там, где вы (или concurrent.futures) создали пул.Таким образом, код верхнего уровня не запускается повторно.

С помощью метода spawn start multiprocessing создает каждый новый дочерний процесс, запуская чистый новый интерпретатор Python, import используя ваш коди затем запустите функцию обслуживания пула.Итак, код верхнего уровня перезапускается как часть import.

...