В чем разница между многопроцессорностью Python и concurrent.futures?

Простой способ реализации многопроцессорности в python:

from multiprocessing import Pool

def calculate(number):
    return number

if __name__ == '__main__':
    pool = Pool()
    result = pool.map(calculate, range(4))

Альтернативная реализация, основанная на фьючерсах,

from concurrent.futures import ProcessPoolExecutor

def calculate(number):
    return number

with ProcessPoolExecutor() as executor:
    result = executor.map(calculate, range(4))

Оба варианта делают по существу одно и то же, но одно поразительное отличие состоит в том, что нам не нужно защищать код с помощью обычного предложения if __name__ == '__main__'. Это потому, что реализация фьючерсов заботится об этом или у нас есть другая причина?

В более широком смысле, в чем разница между multiprocessing и concurrent.futures? Когда одно предпочтительнее другого?

РЕДАКТИРОВАТЬ: Мое первоначальное предположение, что охранник if __name__ == '__main__' необходим только для многопроцессорной обработки, было неверным. По-видимому, этот сторож нужен для обеих реализаций в Windows, в то время как в unix-системах он не нужен.


person David Zwicker    schedule 22.07.2014    source источник
comment
Эм. Я сомневаюсь, что вам не нужен охранник if. Согласно документации ProcessPoolExecutor построен на основе multiprocessing, и поэтому он должен страдать от той же проблемы (иначе документация multiprocessing показала бы, как избежать этого охранника, верно?). На самом деле пример из документации использует обычную защиту.   -  person Bakuriu    schedule 22.07.2014
comment
Ты прав. Я запутался, так как это нужно только на окнах, видимо. Я должен признать, что я тестировал фьючерсы только на Mac и поэтому обнаружил, что защита не нужна. Я добавлю примечание к вопросу, подчеркнув это.   -  person David Zwicker    schedule 22.07.2014
comment
Однажды я вырубил блейд-сервер, забыв об этом охраннике :)   -  person JamesHutchison    schedule 02.02.2017
comment
См. также stackoverflow.com/questions /20776189/   -  person max    schedule 06.05.2017
comment
Похоже, что модель prefork в Unix спасает вас от этого бита, у которого всегда должна быть эта строка «если». Кто-нибудь может подтвердить?   -  person Trinh Hoang Nhu    schedule 09.04.2020


Ответы (2)


На самом деле вы также должны использовать защиту if __name__ == "__main__" с ProcessPoolExecutor: она использует multiprocessing.Process для заполнения своего Pool под обложками, точно так же, как multiprocessing.Pool, поэтому применяются все те же предостережения относительно возможности выбора (особенно в Windows) и т. д.

Я считаю, что ProcessPoolExecutor в конечном итоге должен заменить multiprocessing.Pool, согласно этому заявлению, сделанному Джесси Ноллер ( основной участник Python), когда его спросили, почему у Python есть оба API:

Нам с Брайаном нужно поработать над консолидацией, которую мы намереваемся (редактировать), когда люди освоятся с API. Моя конечная цель состоит в том, чтобы удалить все, кроме базового материала multiprocessing.Process/Queue, из MP в concurrent.* и поддерживать для него многопоточные серверные части.

На данный момент ProcessPoolExecutor в основном делает то же самое, что и multiprocessing.Pool, но с более простым (и более ограниченным) API. Если вам сойдет с рук использование ProcessPoolExecutor, используйте его, потому что я думаю, что в долгосрочной перспективе это с большей вероятностью даст улучшения. Обратите внимание, что вы можете использовать все помощники из multiprocessing с ProcessPoolExecutor, такие как Lock, Queue, Manager и т. д., поэтому необходимость в них не является причиной для использования multiprocessing.Pool.

Однако есть некоторые заметные различия в их API и поведении:

  1. Если процесс в ProcessPoolExecutor внезапно завершается, возникает исключение BrokenProcessPool вызывается, прерывая любые вызовы, ожидающие выполнения работы пулом, и предотвращая отправку новой работы. Если то же самое произойдет с multiprocessing.Pool, он молча заменит завершенный процесс, но работа, которая выполнялась в этом процессе, никогда не будет завершена, что, вероятно, приведет к тому, что вызывающий код зависнет навсегда в ожидании завершения работы.

  2. Если вы используете Python 3.6 или ниже, поддержка initializer/initargs отсутствует в ProcessPoolExecutor. Поддержка этого была добавлена ​​только в версии 3.7).

  3. В ProcessPoolExecutor нет поддержки для maxtasksperchild.

  4. concurrent.futures не существует в Python 2.7, если только вы не установите backport вручную.

  5. Если вы используете Python ниже 3.5, согласно этому вопросу, multiprocessing.Pool.map превосходит ProcessPoolExecutor.map. Обратите внимание, что разница в производительности на рабочий элемент очень мала, поэтому вы, вероятно, заметите большую разницу в производительности, только если используете map для очень большой итерации. Причина разницы в производительности заключается в том, что multiprocessing.Pool группирует итерируемый объект, переданный для сопоставления, в фрагменты, а затем передает фрагменты рабочим процессам, что снижает накладные расходы IPC между родительским и дочерним процессами. ProcessPoolExecutor всегда (или по умолчанию, начиная с 3.5) передает по одному элементу из итерируемого объекта дочерним элементам, что может привести к значительному снижению производительности с большими итерируемыми объектами из-за увеличения накладных расходов IPC. Хорошей новостью является то, что эта проблема исправлена ​​в Python 3.5, так как аргумент ключевого слова chunksize был добавлен к ProcessPoolExecutor.map, который можно использовать для указания большего размера фрагмента, когда вы знаете, что имеете дело с большими итерируемыми объектами. См. эту ошибку для получения дополнительной информации.

person dano    schedule 22.07.2014
comment
Из текущего исходника для ProcessPoolExecutor .map , используя размер фрагмента > 1, похоже, что кортежи будут отправлены в функцию, поэтому функция должна иметь возможность обрабатывать кортежи элементов, а не отдельные элементы. Как вы думаете, я правильно это истолковал? - person wwii; 07.01.2019
comment
@wwii Кортеж, возвращаемый этой функцией, обрабатывается методом _process_chunk, который извлекает каждую запись в кортеже и передает ее предоставленной пользователем функции сопоставления. Таким образом, пользователю не нужно ничего менять, когда он использует размер фрагмента › 1. - person dano; 07.01.2019
comment
@Jay Нет, оба недостатка устранены. chunksize был добавлен к map в версии 3.5, а initializer/initargs — в версии 3.7. - person dano; 29.05.2020

if __name__ == '__main__': просто означает, что вы вызвали скрипт в командной строке, используя python <scriptname.py> [options] вместо import <scriptname> в оболочке Python.

Когда вы вызываете сценарий из командной строки, вызывается метод __main__. Во втором блоке,

with ProcessPoolExecutor() as executor:
    result = executor.map(calculate, range(4))

блок выполняется независимо от того, был ли он вызван из командной строки или импортирован из оболочки.

person Community    schedule 22.07.2014
comment
На самом деле необходимо защитить __main__ скрипта multiprocessing в Windows, так как основное тело повторно выполняется в дочерних процессах. - person Antti Haapala; 22.07.2014
comment
Ааа, в таком случае я неправильно понял вопрос. - person ; 22.07.2014