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
,