- Python. Урок 24. Потоки и процессы в Python. Часть 3. Управление процессами
- Процессы в Python
- Класс Process
- Создание и ожидание завершения работы процессов
- Создание классов-наследников от Process
- Принудительное завершение работы процессов
- Процессы-демоны
- P.S.
- Завершение программы в Python
- 2 ответа 2
- Класс Process() модуля multiprocessing в Python.
- Создание отдельного процесса, запуск задачи и получение результата выполнения.
- Синтаксис:
- Параметры:
- Возвращаемое значение:
- Описание:
- Методы объекта Process .
- Process.run() :
- Process.start() :
- Process.join([timeout]) :
- Process.name :
- Process.is_alive() :
- Process.daemon :
- Process.pid :
- Process.exitcode :
- Process.authkey :
- Process.sentinel :
- Process.terminate() :
Python. Урок 24. Потоки и процессы в Python. Часть 3. Управление процессами
Продолжаем изучать параллельное программирование на Python. На этот раз разберемся с тем как создавать процессы, управлять завершением их работы и изучим особенности процессов-демонов.
Процессы в Python
Процессы в Python позволяют запустить выполнение нескольких задач в параллельном режиме. По сути, при старте процесса запускает еще одна копия интерпретатора Python , в котором выполняется указанная функция. Таким образом, если мы запустим пять процессов, то будет запущено пять отдельных интерпретаторов, в этом случае уже не будет проблем с GIL . Такой способ позволяет параллельно запускать задачи активно использующие CPU . Они будут распределяться между несколькими процессами (ядрами), что значительно увеличит производительность вычислений.
Класс Process
Классом, который отвечает за создание и управление процессами является Process из пакета multiprocessing . Он совместим по сигнатурам методов и конструктора с threading . Thread , это сделано для более простого перехода от многопотокового приложения к многопроцессному. Помимо одноименных с Thread методов, класс Process дополнительно предоставляет ряд своих. Познакомимся поближе с этим классом, конструктор класса выглядит следующим образом:
class multiprocessing.Process(group=None, target=None, name=None, args=(), kwargs=<>, *, daemon=None)
- group
- Параметр всегда равен None , используется для обратной совместимости с Thread .
- target
- Сallable -объект, который будет вызван методом run() при старте процесса.
- name
- Имя процесса.
- args
- kwargs
- Параметры объекта, переданного через target .
- daemon
- Флаг, определяющий является ли данный процесс демоном ( True ) или нет ( False ). Если значение не задано, то оно будет равно свойству daemon родительского процесса.
Помимо методов и свойств, которые совпадают по назначению с аналогичными для класса Thread , класс Process имеет ряд уникальных:
- pid
- Возвращает ID процесса. Если процесс ещё не запущен, то вернет None .
- exitcode
- Код возврата. Если процесс ещё не завершил свою работу, то вернет None .
- authkey
- Ключ аутентификации процесса. При инициализации multiprocessing, с главным процессом связывается специальная строка, которая генерируется с помощью os.urandom() . Это значение наследуют процессы-потомки, но его можно изменить, задав новое значение, через данное свойство.
- sentinel
- Числовой идентификатор, который может использоваться для синхронизации. Подробно об этом будет написано в одном из следующих уроков.
- close()
- Освобождает все ресурсы, связанные с процессом. Если процесс еще работает, то вызов метода приведет к выбросу исключения ValueError .
Создание и ожидание завершения работы процессов
Как уже было сказано выше, за организацию работы с процессами в Python отвечает класс Process . Для запуска процесса используется метод start() . Разберем простой пример:
В рамках данного примера мы написали функцию (строка 4), которая будет выполнена в отдельном процессе, в главном процессе мы создали переменную типа Process (строка 10) с указанием функции func , и запустили его (срока 11). В результате в консоли будут выведены два сообщения из родительского и дочернего процессов:
За ожидание завершения работы процесса(ов) отвечает метод join , со следующей сигнатурой:
При вывозе метода join() выполнение программы будет остановлено до тех пор пока соответствующий процесс не завершит работу. Параметр timeout отвечает за время ожидания завершения работы процесса, если указанное время прошло, а процесс еще не завершился, то ожидание будет прервано и выполнение программы продолжится дальше. В случае, если метод join() завершился по таймауту или в результате того, что процесс был завершен аварийно (терминирован), то он вернет None .
Если в приведенном выше примере код главного процесса заменить на следующий:
То в консоли сообщения будут выведены в таком порядке:
Это происходит потому, что строка print(“Goodbye”) из основного процесса выполняется раньше, чем print(“Hello from child Process”) из дочернего. Для того, чтобы все было выведено в нужном порядке, воспользуемся методом join() :
Для проверки того выполняется процесс сейчас или нет используется метод is_alive() . Дополним наш пример соответствующими проверками:
Создание классов-наследников от Process
В классе наследнике от Process необходимо переопределить метод run() для того, чтобы он (класс) соответствовал протоколу работы с процессами. Ниже представлен пример с реализацией этого подхода.
Мы создали класс CustomProcess , который является наследником от Process и переопределили метод run() , так, что он выводит заданное количество сообщений, которое задается при создании объекта, с интервалом в 500 мс. Запуск процесса осуществляется с помощью метода start() .
Принудительное завершение работы процессов
В отличии от потоков, работу процессов можно принудительно завершить, для этого класс Process предоставляет набор методов:
- terminate()
- Принудительно завершает работу процесса. В Unix отправляется команда SIGTERM , в Windows используется функция TerminateProcess() .
- kill()
- Метод аналогичный terminate() по функционалу, только вместо SIGTERM в Unix будет отправлена команда SIGKILL .
Процессы-демоны
Процессы демоны по своим свойствам похожи на потоки-демоны, их суть заключается в том, что они завершают свою работу, если завершился родительский процесс.
Указание на то, что процесс является демоном должно быть сделано до его запуска (до вызова метода start()) . Для демонического процесса запрещено самостоятельно создавать дочерние процессы. Эти процессы не являются демонами (сервисами) в понимании Unix , единственное их свойство – это завершение работы вместе с родительским процессом.
Указать на то, что процесс является демоном можно при создании экземпляра класса через аргумент daemon , либо после создания через свойство daemon .
При запуске данной программы на консоли должно появиться следующее:
P.S.
Вводные уроки по “Линейной алгебре на Python” вы можете найти соответствующей странице нашего сайта . Все уроки по этой теме собраны в книге “Линейная алгебра на Python”.
Если вам интересна тема анализа данных, то мы рекомендуем ознакомиться с библиотекой Pandas. Для начала вы можете познакомиться с вводными уроками. Все уроки по библиотеке Pandas собраны в книге “Pandas. Работа с данными”.
Завершение программы в Python
Как сделать раннее завершение программы в Python? В самоучителе я нашёл несколько примеров:
Однако там не было объяснения какой метод лучше. Какой метод является наиболее «безаварийным»?
И заодно: есть ли в Python понятие Autocloseable объектов? Если я сделаю ранее завершение программы, нужно ли мне будет закрывать файлы и т.д.?
2 ответа 2
Короткий ответ:
Лучше использовать sys.exit()
Механизм завершения процесса в Python реализован через бросание исключения SystemExit , таким образом можно просто создать подобное исключение и программа завершится:
Функция exit и аналогичная ей quit созданы для удобства работы в интерактивном режиме и их не рекомендуется использовать внутри скриптов:
They are useful for the interactive interpreter shell and should not be used in programs.
По факту они также просто поднимают исключение, и при попытке вызова без скобок напишут подсказку о правильном способе выхода из интерпретатора:
Использовать sys.exit стоит потому, что эта функция лежит в стандартном модуле и будет всегда там доступна. Также это довольно явный способ выразить своё желание завершить программу.
Есть также дополнительный метод для немедленного завершения программы: os._exit . У него довольно специфическая область применения, и там же есть замечание:
The standard way to exit is sys.exit(n)
Т.е. здесь даётся подтверждение того, что стандартный способ завершения программы — это вызов sys.exit .
Функция os.abort , упомянутая вами, использует механизм сигналов процессу. Конкретно при вызове этой функции будет передан сигнал SIGABRT , что в linux приведёт к завершению программы и созданию дампа памяти процесса. Подобное завершение рассматривается операционной системой как аварийное, поэтому не стоит использовать его для безаварийного завершения приложения.
По второй части вопроса. В Python есть развитая система контекстных менеджеров: классов, которые умеют работать с оператором with . Самое частое использование этого механизма встречается, вероятно, с файлами.
Этот код откроет файл, напечатает его содержимое на экран и закроет файл автоматически, даже если возникнет исключение при его печати.
Для классов, которые не приспособлены для работы с with есть функция closing в библиотеке contextlib . Из документации:
is equivalent to this:
Вот небольшой пример работы этой функции:
Теперь небольшое отступление о том, почему стоит использовать конструкцию with .
Известно, что программа завершится от любого необработанного исключения, а не только от SystemExit . Таким образом, если в вашем коде используются какие-то ресурсы, которые требуется правильным образом закрывать перед завершением работы, нужно оборачивать работу с ними в блоки try . finally . .
Однако, при использовании конструкции with это оборачивание происходит автоматически, и все ресурсы закрываются корректно.
Так как выход из программы — это всего лишь брошенное исключение, то и в случае использования функции sys.exit закрытие открытых в операторе with ресурсов произойдёт корректно:
Вы можете писать также и свои классы, предоставляющие ресурсы или классы, оборачивающие другие, которые нужно уметь закрывать автоматически. Для этого используются методы __enter__ и __exit__ .
Класс Process() модуля multiprocessing в Python.
Создание отдельного процесса, запуск задачи и получение результата выполнения.
Синтаксис:
Параметры:
Конструктор всегда следует вызывать с ключевыми аргументами.
- group=None — не используется модулем,
- target=None — вызываемый объект (функция),
- name=None — имя процесса,
- args=() — аргументы для target ,
- kwargs=<> — ключевые аргументы для target ,
- daemon=None — флаг для демонизации процесса.
Возвращаемое значение:
Описание:
Класс Process() модуля multiprocessing запускает вызываемый объект target на исполнение, который будет выполняется в отдельном процессе/ядре процессора.
Есть два способа запустить какое либо действие:
- Передать вызываемый объект (функцию) target в конструктор.
- Переопределить метод Process.run() в подклассе.
Внимание! Никакие другие методы не должны переопределяться в подклассе (кроме конструктора). Другими словами, можно переопределять только методы __init__() и Process.run() этого класса.
Объект Process имеет эквиваленты всех методов объекта Thread() .
Аргумент group всегда должна быть None ; он существует исключительно для совместимости с классом threading.Thread() .
Аргумент target — это вызываемый объект, который будет вызываться методом Process.run() . По умолчанию это None , что означает, что ничего не вызывается. По умолчанию в вызываемый объект target не передаются его аргументы.
Аргумент name — это имя процесса, подробнее смотрите атрибут Process.name .
Аргумент args — это кортеж аргументов для целевого вызова target . Аргумент kwargs — это словарь ключевых аргументов для целевого вызова target .
Если предоставлен ключевой аргумент daemon , то он устанавливает флаг демона процесса значение True или False . Если daemon=None (по умолчанию), этот флаг будет унаследован от процесса создателя.
Если подкласс переопределяет конструктор класса multiprocessing.Process() , то необходимо вызывать конструктор базового класса Process.__init__() , прежде чем делать что-либо еще с процессом.
Обратите внимание, что методы Process.start() , Process.join() , Process.is_alive() , Process.terminate() и Process.exitcode должны вызываться только процессом, создавшим этот объект процесса.
Методы объекта Process .
Объекты Process представляют собой действия, которые выполняются в отдельном процессе.
Process.run() :
Метод Process.run() представляет активность процесса.
Можно переопределить этот метод в своем подклассе. Стандартный метод Process.run() вызывает вызываемый объект, переданный конструктору в качестве целевого аргумента target , с позиционными и ключевыми аргументами, взятыми из аргументов args и kwargs соответственно.
Метод Thread.run() можно переопределить в пользовательском подклассе. Например:
Process.start() :
Метод Process.start() запускает экземпляр Process в отдельном процессе/ядре процессора.
Этот метод должен быть вызван не более одного раза для каждого объекта процесса. Он организует вызов метода Process.run() объекта в отдельном процессе/ядре процессора.
Дополнительно, смотрите описание этого метода у объекта Thread , примеры кода аналогичны.
Process.join([timeout]) :
Метод Process.join() ждет, пока не завершится процесс.
Если необязательный аргумент timeout=None (по умолчанию), то процесс, из которого вызван этот метод, блокируется до тех пор, пока не завершится процесс, у которого вызван этот метод. Проще говоря, если вызвать метод Process.join() для создаваемых процессов из основного процесса программы, то дальнейшее выполнение программы будет заблокировано, пока созданные процессы не завершаться.
Если тайм-аут timeout является положительным числом, то процесс блокируется не более чем на величину секунд, указанную в timeout . Обратите внимание, что метод возвращает None , если его процесс завершается или если время ожидания метода истекает. Проверьте код выхода процесса Process.exitcode , чтобы определить, завершился ли он.
К процессу можно присоединяться много раз.
Процесс не может присоединиться к самому себе, так как это вызовет взаимоблокировку. Попытка присоединиться к процессу до его запуска является ошибкой.
Дополнительно, смотрите описание этого метода у объекта Thread , примеры кода аналогичны.
Process.name :
Атрибут Process.name это имя процесса и представляет собой строку, которая используется только для идентификации. У имени нет семантики.
Несколько процессов могут иметь одно и то же имя (можно присваивать).
Начальное имя задается конструктором класса. Если в конструктор не передан аргумент name , то имена порожденных процессов будет выглядеть как «Process-N1:N2. Nk», где каждый Nk является N-м потомком своего родителя.
Дополнительно, смотрите описание этого метода у объекта Thread , примеры кода аналогичны.
Process.is_alive() :
Метод Process.is_alive() проверяет, является ли процесс живым.
Грубо говоря, объект процесса является живым с момента вызова метода Process.start() и до завершения порожденного дочернего процесса.
Process.daemon :
Атрибут Process.daemon флаг демона процесса, логическое значение. Этот атрибут должен быть установлен перед вызовом метода Process.start() .
Начальное значение наследуется от процесса создателя.
Когда процесс завершается, он пытается завершить все свои демонические дочерние процессы.
Обратите внимание, что демоническому процессу не разрешается создавать дочерние процессы. Так как если демонический процесс будет прерван выходом из родительского процесса, то демонический процесс оставил бы свои дочерние процессы «сиротами«. Кроме того, это не демоны или службы Unix, это обычные процессы, которые будут завершены (но не присоединены), если завершились не-демонические процессы.
Process.pid :
Атрибут Process.pid возвращает идентификатор процесса. До того, как процесс будет порожден, это будет None .
Process.exitcode :
Атрибут Process.exitcode возвращает код выхода порожденного процесса. Значение будет None , если процесс еще не завершен.
Отрицательное значение -N указывает, что дочерний элемент был прерван сигналом N .
Process.authkey :
Атрибут Process.authkey ключ аутентификации процесса, представляет из себя байтовую строку.
При инициализации модуля multiprocessing , основному процессу присваивается случайная строка с помощью os.urandom() .
Когда создается объект процесса, то он наследует ключ аутентификации своего родительского процесса, хотя это может быть изменено путем установки атрибута authkey в другую байтовую строку.
Process.sentinel :
Атрибут Process.sentinel числовой дескриптор системного объекта, который станет «готовым» по завершении процесса.
Можно использовать это значение, если необходимо дождаться нескольких событий одновременно, используя multiprocessing.connection.wait() . Вообще это проще сделать вызвав метод Process.join() у каждого из создаваемых процессов, результатов которых надо дождаться.
В Windows это дескриптор ОС, который можно использовать с семейством вызовов API WaitForSingleObject и WaitForMultipleObjects . В Unix это файловый дескриптор, который можно использовать с примитивами из модуля select .
Process.terminate() :
Метод Process.terminate() завершает процесс. В Unix это делается с помощью сигнала SIGTERM , в Windows используется TerminateProcess() .
Обратите внимание, что обработчики выхода, предложения tru/finally и т. д. не будут выполняться.