вирішити серед підпроцесів, багатопроцесових і потокових в 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, ви часто обмежуєтесь одним ядром, навіть якщо, як зазначається в одному з коментарів, Global Lopre Lock Lock (GIL) може бути випущений у коді C, який називається з коду Python) .

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


1
для призначення я просто використав модуль "багатопроцесорний" та його метод pool.map (). шматок торту !
kmonsoor

Чи розглядається така річ, як селера? Чому це так чи ні?
користувач3245268

Наскільки я можу сказати, селера більше задіяна (ви повинні встановити якийсь брокер повідомлень), але це, можливо, варіант, який, мабуть, слід розглянути, залежно від проблеми.
Ерік О Лебігот

186

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

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

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

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

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

(Оновлення: 7 серпня 2019: Підтримка Python 3 для підпроцесів айнсіо : підпроцеси асинціо )

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()- це подія --- або одна, що включає вхід (доступний для читання) на деякій кількості розеток або дескрипторів файлів, або буферний простір, який стає доступним для деяких інших (доступних для запису) дескрипторів або розеток, деякі виняткові умови (TCP наприклад, пакети PUSH'd поза діапазону або TIMEOUT.

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

Я особисто вважаю це ім'я, скручене як сприятливе для моделі програмування ... оскільки ваш підхід до проблеми повинен бути, в деякому сенсі, "скрученим" зсередини. Замість того, щоб мислити свою програму як низку операцій над вхідними даними та результатами чи результатами, ви пишете свою програму як послугу чи демон і визначаєте, як вона реагує на різні події. (Насправді основним "головним циклом" програми Twisted є (як правило? Завжди?) А reactor()).

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

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

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

Розподілений варіант:

Ще одна сфера обробки, про яку ви не питали, але яку варто врахувати, - це розподілена обробка. Існує багато інструментів і рамок Python для розподіленої обробки та паралельних обчислень. Особисто я вважаю, що найпростішим у користуванні є той, який найменш часто вважається у цьому просторі.

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

Звичайно, коли ви починаєте створювати масштабніші та складніші рішення навколо Redis, ви повторно реалізуєте багато функцій, які вже вирішені за допомогою, Celery , Apache Spark і Hadoop , Zookeeper , etc.d , Cassandra тощо. Усі вони мають модулі для доступу Python до своїх послуг.

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

Висновок

Там у вас є гама альтернатив обробки для Python - від однопотокових, з простими синхронними викликами до підпроцесів, пулів опитуваних підпроцесів, потокових та багатопроцесових, керованих подіями кооперативної багатозадачності та виходу до розподіленої обробки.


1
Важко використовувати багатопроцесорну роботу з класами / OOP, хоча.
Tjorriemorrie

2
@Tjorriemorrie: Я хочу здогадатися, що ви маєте на увазі, що важко відправити виклики методу до примірників об'єктів, які можуть бути в інших процесах. Я б припустив, що це та сама проблема, яку ви маєте з нитками, але легше помітною (а не тендітною та підпорядкованою умовам гонки). Я думаю, що рекомендованим підходом було б організувати, щоб вся така відправка відбувалась через об’єкти черги, які працюють з однопотоковими, багатопотоковими та поперечними процесами. (З деякою реалізацією Redis або Celery Queue, навіть через групу вузлів)
Jim Dennis

2
Це дійсно гарна відповідь. Я б хотів, щоб це було у вступі до паралельності в документах Python3.
корінь-11

1
@ root-11, ви можете запропонувати його зберігачам документів; Я опублікував його тут для безкоштовного використання. Ви та вони можуть використовувати його цілком або частинами.
Джим Денніс

"Для мене це насправді досить просто:" Любіть це. велике спасибі
jerome

5

У подібному випадку я вибрав окремі процеси та небагато необхідного для зв'язку через мережеву розетку. Це дуже портативно і досить просто зробити за допомогою python, але, ймовірно, не простіше (у моєму випадку я мав ще одне обмеження: спілкування з іншими процесами, написаними на C ++).

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


4

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


5
Це не зовсім вірно, AFAIK ви можете випустити GIL за допомогою API API, а є й інші реалізації Python, такі як IronPython або Jython, які не зазнають таких обмежень. Я, проте, не спростував.
Бастієн Леонард

1

Викладіть і дозвольте Unix виконувати ваші завдання:

використовуйте iterpipes, щоб обернути підпроцес, а потім:

З сайту Теда Зюби

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

АБО

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

Ви спілкуєтесь з GIL, поки ви відправляєте задніх хлопців, щоб виконати свою багатоядерну роботу.


6
"Переносимість важлива тим, що я хотів би, щоб ця програма працювала на будь-якій версії Python на Mac, Linux та Windows."
detly

Чи можна за допомогою цього рішення неодноразово взаємодіяти з роботою? Ви можете це зробити в багатопроцесорній роботі, але я не думаю, що це в підпроцесі.
abalter
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.