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

Для следующего скрипта (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 ответ

Решение

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

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

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

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

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

Вы, вероятно , не хотите защищать importвот так. Ведь стоимость звонка import pandas as pd Один раз на ядро ​​может показаться нетривиальным, но это происходит только при запуске, и затраты на запуск тяжелой функции, связанной с процессором, в миллионы раз полностью ее затопят. (Если нет, то вы, вероятно, не хотели использовать многопроцессорность в первую очередь…) И, как правило, то же самое относится и к def а также class операторы (особенно, если они не захватывают какие-либо переменные замыкания или что-то еще). Это только код установки, который некорректно запускаться несколько раз (например, 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таким образом, тот же код может работать при тестировании на одной платформе, но может не работать при развертывании на другой).


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

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

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

Другие вопросы по тегам