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

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

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

Есть мысли по этому поводу? Я хотел бы самое простое решение, которое является портативным.

6 ответов

Решение

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

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

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

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

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

Опция подпроцесса:

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

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

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

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

Многопроцессорная опция:

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

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

Опция потоков:

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

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

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

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

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

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

Витой вариант:

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

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

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

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

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

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

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

(Примечание: более новые версии Python 3.x включают в себя такие функции asyncio (асинхронный ввод / вывод), как async def, декоратор @ async.coroutine и ключевое слово await, а также выход из будущей поддержки. Все они примерно аналогичны Скрученный с точки зрения процесса (кооперативной многозадачности).

Распределенная опция:

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

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

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

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

Заключение

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

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

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

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

Оболочка и пусть Unix, чтобы сделать вашу работу:

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

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

INPUTS_FROM_YOU | xargs -n1 -0 -P NUM ./process #NUM параллельные процессы

ИЛИ ЖЕ

Гну Параллель также будет служить

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

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

В конце концов, лучшим результатом было использование grequests или a, который быстро превратился в aiohttp.

grequests: асинхронные запросы с запросами Python

стартер aiohttp: https://pythonalgos.com/send-api-requests-asynchronously-in-python/

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