выбор между подпроцессом, многопроцессорностью и потоком в Python?

110

Я хотел бы распараллелить мою программу Python, чтобы она могла использовать несколько процессоров на машине, на которой она работает. Мое распараллеливание очень простое, поскольку все параллельные «потоки» программы независимы и записывают свой вывод в отдельные файлы. Мне не нужны потоки для обмена информацией, но я обязательно знаю, когда потоки завершаются, поскольку некоторые шаги моего конвейера зависят от их вывода.

Переносимость важна, поскольку я хотел бы, чтобы это работало на любой версии Python на Mac, Linux и Windows. С учетом этих ограничений, какой модуль Python является наиболее подходящим для реализации этого? Я пытаюсь выбрать между потоком, подпроцессом и многопроцессорностью, которые, похоже, предоставляют связанные функции.

Есть мысли по этому поводу? Мне нужно самое простое портативное решение.

Вайбхав Мул
источник
Связано: stackoverflow.com/questions/1743293/… (прочтите мой ответ там, чтобы понять, почему потоки не подходят для кода на чистом Python)
1
«Любая версия Python» слишком расплывчата. Python 2.3? 1.x? 3.x? Это просто невыполнимое условие.
Detly

Ответы:

64

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

Этот subprocessмодуль также позволит вам запускать несколько процессов, но я обнаружил, что он менее удобен в использовании, чем новый модуль многопроцессорности.

Потоки, как известно, тонкие, и с CPython вы часто ограничены одним ядром с ними (хотя, как отмечено в одном из комментариев, глобальная блокировка интерпретатора (GIL) может быть выпущена в коде C, вызываемом из кода Python) .

Я считаю, что большинство функций трех модулей, которые вы цитируете, можно использовать независимо от платформы. Что multiprocessingкасается переносимости, обратите внимание, что он входит в стандартную комплектацию только начиная с Python 2.6 (хотя версия для некоторых более старых версий Python существует). Но это отличный модуль!

Эрик О Лебигот
источник
1
для задания я просто использовал модуль "multiprocessing" и его метод pool.map (). Кусок пирога !
kmonsoor
Рассматривается ли такая вещь, как сельдерей? Почему так или нет?
user3245268
Насколько я могу судить, Celery более задействован (вам нужно установить какой-то брокер сообщений), но это вариант, который, вероятно, следует рассмотреть, в зависимости от решаемой проблемы.
Эрик О Лебигот
186

Для меня это на самом деле довольно просто:

Вариант подпроцесса :

subprocessпредназначен для запуска других исполняемых файлов --- это в основном оболочка вокруг os.fork()и os.execve()с некоторой поддержкой для необязательного подключения (настройка PIPE для подпроцессов и обратно. Очевидно, что вы можете использовать другие механизмы межпроцессного взаимодействия (IPC), такие как сокеты или Posix или Общая память SysV. Но вы будете ограничены любыми интерфейсами и каналами IPC, которые поддерживаются программами, которые вы вызываете.

Обычно используется любая subprocessсинхронная функция - просто вызывая некоторую внешнюю утилиту и считывая ее вывод или ожидая ее завершения (возможно, считывая ее результаты из временного файла или после того, как он отправил их в некоторую базу данных).

Однако можно создать сотни подпроцессов и опросить их. Мой любимый служебный класс делает именно это. Самый большой недостаток в subprocessмодуле является то , что поддержка ввода / вывода , как правило блокировки. Существует черновик PEP-3145, чтобы исправить это в какой-то будущей версии Python 3.x, и альтернативный asyncproc (предупреждение, которое ведет прямо к загрузке, а не к какой-либо документации или README). Я также обнаружил, что относительно легко просто импортировать файловые дескрипторы PIPE fcntlи управлять ими Popenнапрямую, хотя я не знаю, переносится ли это на платформы, отличные от UNIX.

(Обновление: 7 августа 2019 г .: поддержка Python 3 для подпроцессов ayncio : подпроцессы asyncio )

subprocess почти не имеет поддержки обработки событий ... хотя вы можете использовать signalмодуль и простые сигналы UNIX / Linux старой школы - как бы мягко убивая ваши процессы.

Вариант многопроцессорности :

multiprocessingпредназначен для выполнения функций в существующем (Python) коде с поддержкой более гибкого взаимодействия между этим семейством процессов. В частности, лучше всего строить свой multiprocessingIPC вокруг Queueобъектов модуля, где это возможно, но вы также можете использовать Eventобъекты и различные другие функции (некоторые из которых, предположительно, построены на mmapподдержке платформ, на которых этой поддержки достаточно).

multiprocessingМодуль Python предназначен для предоставления интерфейсов и функций, которые очень похожи на threading то, что позволяет CPython масштабировать вашу обработку между несколькими процессорами / ядрами, несмотря на GIL (Global Interpreter Lock). Он использует все усилия по детальной блокировке SMP и согласованности, которые были предприняты разработчиками ядра вашей ОС.

Вариант заправки :

threadingпредназначен для довольно узкого диапазона приложений, которые связаны с вводом-выводом (не нужно масштабировать на несколько ядер ЦП) и которые выигрывают от чрезвычайно низкой задержки и накладных расходов на переключение при переключении потоков (с общей памятью ядра) по сравнению с процессом / переключение контекста. В Linux это почти пустой набор (время переключения процессов Linux очень близко к его переключателям потоков).

threadingу Python есть два основных недостатка .

Один из них, конечно, зависит от реализации - в основном затрагивает CPython. Это GIL. По большей части, большинство программ CPython не выиграют от доступности более двух процессоров (ядер), и часто производительность будет снижаться из-за блокировки GIL.

Более серьезная проблема, не зависящая от реализации, заключается в том, что потоки совместно используют одну и ту же память, обработчики сигналов, дескрипторы файлов и некоторые другие ресурсы ОС. Таким образом, программист должен быть чрезвычайно осторожен с блокировкой объектов, обработкой исключений и другими аспектами своего кода, которые являются тонкими и могут убить, остановить или заблокировать весь процесс (набор потоков).

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

  • (Примечание: использование threadingс основными системами Python, такими как NumPy , может значительно меньше пострадать от конкуренции с GIL, чем большая часть вашего собственного кода Python. Это потому, что они были специально разработаны для этого; собственные / двоичные части NumPy, например, выпустит GIL, когда это безопасно).

Скрученный вариант:

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

Чтобы понять, как это возможно, следует прочитать об особенностях select()(которые могут быть построены на основе select () или poll () или аналогичных системных вызовов ОС). По сути, все это связано с возможностью сделать запрос ОС в спящий режим в ожидании любой активности в списке файловых дескрипторов или некоторого тайм-аута.

Пробуждение от каждого из этих вызовов select()является событием: либо вход, доступный (читаемый) на некотором количестве сокетов или файловых дескрипторов, либо доступное пространство буферизации на некоторых других (записываемых) дескрипторах или сокетах, некоторые исключительные условия (TCP внеполосные PUSH-пакеты, например) или TIMEOUT.

Таким образом, модель программирования Twisted построена на обработке этих событий и последующем зацикливании результирующего «основного» обработчика, что позволяет ему отправлять события вашим обработчикам.

Я лично считаю, что название Twisted напоминает модель программирования ... поскольку ваш подход к проблеме должен быть в каком-то смысле "вывернут" наизнанку. Вместо того, чтобы рассматривать вашу программу как серию операций с входными данными и выходами или результатами, вы пишете свою программу как службу или демон и определяете, как она реагирует на различные события. (Фактически, основной «основной цикл» программы Twisted - это (обычно? Всегда?) А reactor()).

В основных вызовы с использованием Twisted включает скручивания ума вокруг управляемых событий модели , а также сторониться использование любых библиотек классов или наборов инструментальных средств , которые не писаны сотрудничать в Twisted рамок. Вот почему Twisted предоставляет свои собственные модули для обработки протокола SSH, для curses и свои собственные функции subprocess / Popen, а также многие другие модули и обработчики протоколов, которые, на первый взгляд, могут дублировать элементы стандартных библиотек Python.

Я думаю, что понимать Twisted на концептуальном уровне полезно, даже если вы никогда не собираетесь его использовать. Это может дать представление о производительности, конкуренции и обработке событий в вашей потоковой, многопроцессорной и даже подпроцессной обработке, а также о любой выполняемой вами распределенной обработке.

( Примечание. Новые версии Python 3.x включают функции asyncio (асинхронный ввод-вывод), такие как async def , декоратор @ async.coroutine и ключевое слово await , а также yield from future support. Все они примерно похожи на Искажено с точки зрения процесса (кооперативная многозадачность). (Текущий статус поддержки Twisted для Python 3 см .: https://twistedmatrix.com/documents/current/core/howto/python3.html )

Распределенный вариант:

Еще одна область обработки, о которой вы не спрашивали, но которую стоит рассмотреть, - это распределенная обработка. Существует множество инструментов и сред Python для распределенной обработки и параллельных вычислений. Лично я считаю, что проще всего использовать тот, который реже всего считается находящимся в этом пространстве.

Построить распределенную обработку на основе Redis - почти тривиально . Все хранилище ключей может использоваться для хранения рабочих единиц и результатов, списки Redis LIST могут использоваться как Queue()подобные объекты, а поддержка PUB / SUB может использоваться для Event-подобной обработки. Вы можете хэшировать свои ключи и использовать значения, реплицированные в свободном кластере экземпляров Redis, для хранения топологии и сопоставлений хэш-токенов, чтобы обеспечить согласованное хеширование и переключение при отказе для масштабирования за пределами возможностей любого отдельного экземпляра для координации ваших рабочих и данные маршалинга (маринованные, JSON, BSON или YAML) среди них.

Конечно , как вы начинаете строить большие масштабы и более сложные решения вокруг Redis вы повторно реализации многих функций , которые уже были решены с помощью, сельдерей , Apache Спарк и Hadoop , Zookeeper , etcd , Cassandra и так далее. У всех есть модули для доступа Python к их сервисам.

[Обновление: пара ресурсов для рассмотрения, если вы рассматриваете Python для ресурсоемких вычислений в распределенных системах: IPython Parallel и PySpark . Хотя это распределенные вычислительные системы общего назначения, они являются особенно доступными и популярными подсистемами науки о данных и аналитики].

Вывод

Здесь у вас есть спектр альтернатив обработки для Python, от однопоточной обработки с простыми синхронными вызовами подпроцессов, пулов опрашиваемых подпроцессов, многопоточной и многопроцессорной обработки, совместной многозадачности, управляемой событиями, и до распределенной обработки.

Джим Деннис
источник
1
Однако сложно использовать многопроцессорность с классами / ООП.
Tjorriemorrie
2
@Tjorriemorrie: Я собираюсь предположить, что вы имеете в виду, что сложно отправлять вызовы методов экземплярам объектов, которые могут быть в других процессах. Я бы предположил, что это та же проблема, что и с потоками, но более заметная (а не хрупкая и подверженная неясным условиям гонки). Я бы подумал, что рекомендуемый подход состоял бы в том, чтобы организовать такую ​​отправку через объекты Queue, которые работают в однопоточном, многопоточном режиме и между процессами. (С некоторой реализацией Redis или Celery Queue, даже в кластере узлов)
Джим Деннис
2
Это действительно хороший ответ. Я бы хотел, чтобы это было во введении к параллелизму в документации Python3.
root-11
1
@ root-11 вы можете предложить его разработчикам документа; Я опубликовал его здесь для бесплатного использования. Вы и они можете использовать его целиком или по частям.
Джим Деннис
«Для меня это на самом деле довольно просто:« Мне нравится.
Большое
5

В подобном случае я выбрал отдельные процессы и небольшую часть необходимой связи через сетевой сокет. Он очень портативен и довольно прост в использовании с помощью python, но, вероятно, не проще (в моем случае у меня было еще одно ограничение: связь с другими процессами, написанными на C ++).

В вашем случае я бы, вероятно, пошел на многопроцессорность, поскольку потоки python, по крайней мере, при использовании CPython, не являются настоящими потоками. Что ж, это собственные системные потоки, но модули C, вызываемые из Python, могут или не могут выпускать GIL и разрешать другим потокам их запускать при вызове кода блокировки.

Крисс
источник
4

Чтобы использовать несколько процессоров в CPython, ваш единственный выбор - это multiprocessingмодуль. CPython блокирует свои внутренние компоненты ( GIL ), что предотвращает параллельную работу потоков на других процессорах. multiprocessingМодуль создает новые процессы (как subprocess) и управляет связью между ними.

Йохен Ритцель
источник
5
Это не совсем так, AFAIK вы можете выпустить GIL с помощью C API, и есть другие реализации Python, такие как IronPython или Jython, которые не страдают от таких ограничений. Я не голосовал против.
Bastien Léonard
1

Отключитесь и позвольте unix делать вашу работу:

используйте iterpipes для переноса подпроцесса, а затем:

С сайта Теда Зюбы

INPUTS_FROM_YOU | xargs -n1 -0 -P ЧИСЛО / процесс # ЧИСЛО параллельных процессов

ИЛИ

Gnu Parallel также будет обслуживать

Вы тусуетесь с GIL, пока отправляете закулисных мальчиков выполнять свою многоядерную работу.

чиггси
источник
6
«Переносимость важна, потому что я хотел бы, чтобы это работало на любой версии Python на Mac, Linux и Windows».
Detly
Можно ли с помощью этого решения постоянно взаимодействовать с работой? Вы можете сделать это в многопроцессорном режиме, но я так не думаю в подпроцессе.
abalter