выбор между подпроцессом, многопроцессорностью и потоком в Python? - PullRequest
95 голосов
/ 13 апреля 2010

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

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

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

Ответы [ 5 ]

164 голосов
/ 16 апреля 2013

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

Опция :

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

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

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

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

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

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

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

Опция Threading :

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

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

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

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

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

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

Опция витая :

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

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

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

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

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

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

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

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

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

Еще одна область обработки, о которой вы не спрашивали, но которую стоит рассмотреть, - это распределенная обработка. Существует множество инструментов и сред 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, от однопоточных, с простыми синхронными вызовами до подпроцессов, пулов опрашиваемых подпроцессов, потоковых и многопроцессорных, управляемых событиями кооперативных многозадачностей и от распределенных до распределенных обработка.

58 голосов
/ 13 апреля 2010

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

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

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

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

5 голосов
/ 13 апреля 2010

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

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

4 голосов
/ 13 апреля 2010

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

1 голос
/ 10 декабря 2010

Разложите и позвольте Unix выполнять вашу работу:

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

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

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

OR

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...