Работа с процессами в Python
С появлением многоядерных процессоров стала общеупотребительной практика распространять нагрузку на все доступные ядра. Существует два основных подхода в распределении нагрузки: использование процессов и потоков. О первом мы как раз сейчас и поговорим.
Немного истории
Модуль multiprocessing изначально был добавлен в Python 2.6. Этот модуль позволяет создавать процессы таким же образом, как при создании потоков при помощи модуля threading.
Суть в том, что, в связи с тем, что теперь мы создаем процессы, появляется возможность обойти GIL (Global Interpreter Lock) и воспользоваться возможностью использования нескольких процессоров (или даже ядер) на компьютере.
Пакет multiprocessing
также включает ряд API, которых вообще нет в модуле threading. Например, там есть очень удобный класс Pool
, который можно использовать для параллельного выполнения функции между несколькими входами. Мы рассмотрим Pool
немного позже. А начнем пожалуй с класса Process
модуля multiprocessing
.
Приступим к работе с Multiprocessing
Класс Process
очень похож на класс Thread
модуля threading
. Давайте попробуем создать несколько процессов, которые вызывают одну и ту же функцию, и посмотрим, как это сработает.
Начнем с того, что создадим функцию func
. Внутри func
возводим переданное число number
в квадрат. Мы также используем модуль os для того, чтобы получить id
текущего процесса. С помощью него можно определить какой именно процесс вызывает функцию.
import os from multiprocessing import Process def func(number): proc = os.getpid() print(f'{number} squared to {number ** 2} by process id: {proc}')
Теперь наконец создадим функцию, для создания 5 процессов для 5 целых чисел, и посмотрим что получилось.
def main(): procs = [] numbers = [1, 2, 3, 4, 5] for number in numbers: proc = Process(target=func, args=(number,)) procs.append(proc) proc.start() for proc in procs: proc.join()
Здесь хотелось бы обсудить несколько важных моментов. Во-первых, f-строки работают только в версиях Python 3.6 или выше. Во-вторых, аргументы в функции Process
это всегда кортеж, даже если там всего один элемент.
В функции main
, в нижнем блоке кода, мы создаем несколько процессов и стартуем их с помощью функции start()
.
Самый последний цикл только вызывает метод join()
для каждого из процессов, что говорит Python подождать, пока процесс завершится. Если вам нужно остановить процесс, вы можете вызвать метод terminate()
.
Запускаем полученный скрипт.
if __name__ == '__main__': main()
Вывод будет примерно таким.
1 squared to 1 by process id: 1600 2 squared to 4 by process id: 1601 3 squared to 9 by process id: 1602 4 squared to 16 by process id: 1603 5 squared to 25 by process id: 1604
Иногда приятно иметь читабельное название процессов. К счастью, multiprocessing
дает возможность получить доступ к названию нашего процесса.
from multiprocessing import Process, current_process def func(number): proc = current_process().name print(f'{number} squared to {number ** 2} by process {proc}') def main(): procs = [] numbers = [1, 2, 3, 4, 5] for number in numbers: proc = Process(target=func, args=(number,)) procs.append(proc) proc.start() for proc in procs: proc.join() if __name__ == '__main__': main()
Вывод будет таким.
1 squared to 1 by process Process-1 2 squared to 4 by process Process-2 3 squared to 9 by process Process-3 4 squared to 16 by process Process-4 5 squared to 25 by process Process-5
Кроме того, есть возможность напрямую назначить имя процессу.
proc = Process(target=func, name='My Process', args=(number,))
Класс Pool
Класс Pool
используется для создания пула рабочих процессов. Он включает в себя методы, которые позволяют вам разгружать задачи к рабочим процессам. Давайте посмотрим на простейший пример.
from multiprocessing import Pool def func(number): return number ** 2 def main(): numbers = [1, 2, 3] with Pool(processes=3) as pool: print(pool.map(func, numbers)) if __name__ == '__main__': main()
Здесь я создал экземпляр Pool
с помощью контекстного менеджера with...as...
и указал ему создать три рабочих процесса. Далее я использовал метод map
для отображения функции для каждого процесса. Наконец вывожу результат, который в данном случае является списком [1, 4, 9]
.
Реальный пример
Попробуем применить метод requests.get()
к некоторому списку сайтов. Последовательное выполнение задачи отнимет много времени, но что если сделать это параллельно?
import requests from multiprocessing import Pool def main(processes): urls = [ 'http://www.python.org', 'http://www.python.org/about/', 'http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html', 'http://www.python.org/doc/', 'http://www.python.org/download/', 'http://www.python.org/getit/', 'http://www.python.org/community/', 'https://wiki.python.org/moin/', 'http://planet.python.org/', 'https://wiki.python.org/moin/LocalUserGroups', 'http://www.python.org/psf/', 'http://docs.python.org/devguide/', 'http://www.python.org/community/awards/' ] # создадим пул работников with Pool(processes=processes) as pool: # получим результат с помощью функции map results = pool.map(requests.get, urls)
Теперь сравним скорости обработки при различном размере пула.
if __name__ == '__main__': # -- 13 Pool -- # main(processes=13) # -- 8 Pool -- # main(processes=8) # -- 4 Pool -- # main(processes=4) # -- Single -- # main(processes=1)
Вообще говоря, скорость выполнения таких процессов сильно зависит от скорости и качества интернет соединения. Кроме того, логически более правильно здесь было бы использоватьasync
, а неmultiprocessing
. Однако даже в таком примере виде прирост скорости.
На моей машине получилось вот так:
13 Pool: 2.6185 sec 8 Pool: 2.7000 sec 4 Pool: 3.0071 sec Single: 7.3520 sec
Итог
Хотя с помощью этой заметки вы теперь сможете ускорять свои программы, не стоит забывать и об оптимизации собственного кода. Python в целом достаточно быстр, если знать как на нем писать.
Мы затронули только некоторые простые вопросы, связанные многопроцессорным программированием на Python. Разумеется, в документации Python представлено намного больше развернутой информации, так что рекомендую с ней ознакомиться.
Статья подготовлена образовательной организацией Python Academy