Продолжаем изучать параллельное программирование на Python. На этот раз разберемся с тем как создавать процессы, управлять завершением их работы и изучим особенности процессов-демонов.
- Процессы в Python
- Класс Process
- Создание и ожидание завершения работы процессов
- Создание классов-наследников от Process
- Принудительное завершение работы процессов
- Процессы-демоны
Процессы в 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(). Разберем простой пример:
from multiprocessing import Process def func(): print("Hello from child Process") if __name__ == "__main__": print("Hello from main Process") proc = Process(target=func) proc.start()
В рамках данного примера мы написали функцию (строка 4), которая будет выполнена в отдельном процессе, в главном процессе мы создали переменную типа Process (строка 10) с указанием функции func, и запустили его (срока 11). В результате в консоли будут выведены два сообщения из родительского и дочернего процессов:
Hello from main Process Hello from child Process
За ожидание завершения работы процесса(ов) отвечает метод join, со следующей сигнатурой:
join([timeout])
При вывозе метода join() выполнение программы будет остановлено до тех пор пока соответствующий процесс не завершит работу. Параметр timeout отвечает за время ожидания завершения работы процесса, если указанное время прошло, а процесс еще не завершился, то ожидание будет прервано и выполнение программы продолжится дальше. В случае, если метод join() завершился по таймауту или в результате того, что процесс был завершен аварийно (терминирован), то он вернет None.
Если в приведенном выше примере код главного процесса заменить на следующий:
print("Hello from main Process") proc = Process(target=func) proc.start() print("Goodbye")
То в консоли сообщения будут выведены в таком порядке:
Hello from main Process Goodbye Hello from child Process
Это происходит потому, что строка print(“Goodbye”) из основного процесса выполняется раньше, чем print(“Hello from child Process”) из дочернего. Для того, чтобы все было выведено в нужном порядке, воспользуемся методом join():
print("Hello from main Process") proc = Process(target=func) proc.start() proc.join() print("Goodbye")
Для проверки того выполняется процесс сейчас или нет используется метод is_alive(). Дополним наш пример соответствующими проверками:
print("Hello from main Process") proc = Process(target=func) proc.start() print(f"Proc is_alive status: {proc.is_alive()}") proc.join() print("Goodbye") print(f"Proc is_alive status: {proc.is_alive()}")
Результат выполнения:
Hello from main Process Proc is_alive status: True Hello from child Process Goodbye Proc is_alive status: False
Создание классов-наследников от Process
В классе наследнике от Process необходимо переопределить метод run() для того, чтобы он (класс) соответствовал протоколу работы с процессами. Ниже представлен пример с реализацией этого подхода.
from multiprocessing import Process from time import sleep class CustomProcess(Process): def __init__(self, limit): Process.__init__(self) self._limit = limit def run(self): for i in range(self._limit): print(f"From CustomProcess: {i}") sleep(0.5) if __name__ == "__main__": cpr = CustomProcess(3) cpr.start()
Мы создали класс CustomProcess, который является наследником от Process и переопределили метод run(), так, что он выводит заданное количество сообщений, которое задается при создании объекта, с интервалом в 500 мс. Запуск процесса осуществляется с помощью метода start().
Принудительное завершение работы процессов
В отличии от потоков, работу процессов можно принудительно завершить, для этого класс Process предоставляет набор методов:
- terminate()
- Принудительно завершает работу процесса. В Unix отправляется команда SIGTERM, в Windows используется функция TerminateProcess().
- kill()
- Метод аналогичный terminate() по функционалу, только вместо SIGTERM в Unix будет отправлена команда SIGKILL.
from multiprocessing import Process from time import sleep def func(): counter = 0 while True: print(f"counter = {counter}") counter += 1 sleep(0.1) if __name__ == "__main__": proc = Process(target=func) proc.start() sleep(0.7) proc.terminate()
Процессы-демоны
Процессы демоны по своим свойствам похожи на потоки-демоны, их суть заключается в том, что они завершают свою работу, если завершился родительский процесс.
Указание на то, что процесс является демоном должно быть сделано до его запуска (до вызова метода start()). Для демонического процесса запрещено самостоятельно создавать дочерние процессы. Эти процессы не являются демонами (сервисами) в понимании Unix, единственное их свойство – это завершение работы вместе с родительским процессом.
Указать на то, что процесс является демоном можно при создании экземпляра класса через аргумент daemon, либо после создания через свойство daemon.
from multiprocessing import Process from time import sleep def func(name): counter = 0 while True: print(f"proc {name}, counter = {counter}") counter += 1 sleep(0.1) if __name__ == "__main__": # Указание на то, что процесс демон при создании объекта класса Process proc1 = Process(target=func, args=("proc1",), daemon=True) # Указание на то, что процесс демон через свойство daemon proc2 = Process(target=func, args=("proc2",)) proc2.daemon = True # Запуск процессов proc1.start() proc2.start() sleep(0.3) # Процессы proc1 и proc2 завершаться вместе с родительским процессом # ...
При запуске данной программы на консоли должно появиться следующее:
proc proc1, counter = 0 proc proc2, counter = 0 proc proc1, counter = 1 proc proc2, counter = 1 proc proc1, counter = 2 proc proc2, counter = 2
P.S.
Вводные уроки по “Линейной алгебре на Python” вы можете найти соответствующей странице нашего сайта. Все уроки по этой теме собраны в книге “Линейная алгебра на Python”.
Если вам интересна тема анализа данных, то мы рекомендуем ознакомиться с библиотекой Pandas. Для начала вы можете познакомиться с вводными уроками. Все уроки по библиотеке Pandas собраны в книге “Pandas. Работа с данными”.