Решая между подпроцессом, многопроцессорностью и потоком в Python?

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

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

Любые мысли об этом? Я бы хотел, чтобы простейшее решение было переносимым.

Ответ 1

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

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

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

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

Ответ 2

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

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

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

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

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

(Обновление: 7 августа 2019 года: поддержка Python 3 для подпроцессов ayncio: https://docs.python.org/3/library/asyncio-subprocess.html)

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

Опция многопроцессорной обработки:

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

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

Вариант нарезки резьбы:

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

threading страдает от двумя основными недостатками в Python.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Заключение

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

Ответ 3

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

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

Ответ 4

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

Ответ 5

Выключите оболочку и отпустите unix для выполнения ваших задач:

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

С сайта Ted Ziuba

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

ИЛИ

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

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