Работа с процессами в Python
С появлением многоядерных процессоров стала общеупотребительной практика распространять нагрузку на все доступные ядра. Существует два основных подхода в распределении нагрузки: использование процессов и потоков. О первом мы как раз сейчас и поговорим.
Немного истории
Модуль Для просмотра ссылки ВойдиПакет multiprocessing также включает ряд API, которых вообще нет в модуле threading. Например, там есть очень удобный класс Pool, который можно использовать для параллельного выполнения функции между несколькими входами. Мы рассмотрим Pool немного позже. А начнем пожалуй с класса Process модуля multiprocessing.Суть в том, что, в связи с тем, что теперь мы создаем процессы, появляется возможность обойти GIL (Global Interpreter Lock) и воспользоваться возможностью использования нескольких процессоров (или даже ядер) на компьютере.
Приступим к работе с Multiprocessing
Класс Process очень похож на класс Thread модуля threading. Давайте попробуем создать несколько процессов, которые вызывают одну и ту же функцию, и посмотрим, как это сработает.Начнем с того, что создадим функцию func. Внутри func возводим переданное число number в квадрат. Мы также используем Для просмотра ссылки Войди
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()
В функции main, в нижнем блоке кода, мы создаем несколько процессов и стартуем их с помощью функции start().Здесь хотелось бы обсудить несколько важных моментов. Во-первых, f-строки работают только в версиях Python 3.6 или выше. Во-вторых, аргументы в функции Process это всегда кортеж, даже если там всего один элемент.
Самый последний цикл только вызывает метод 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 = [
'Для просмотра ссылки Войдиили Зарегистрируйся',
'Для просмотра ссылки Войдиили Зарегистрируйся',
'Для просмотра ссылки Войдиили Зарегистрируйся',
'Для просмотра ссылки Войдиили Зарегистрируйся',
'Для просмотра ссылки Войдиили Зарегистрируйся',
'Для просмотра ссылки Войдиили Зарегистрируйся',
'Для просмотра ссылки Войдиили Зарегистрируйся',
'Для просмотра ссылки Войдиили Зарегистрируйся',
'Для просмотра ссылки Войдиили Зарегистрируйся',
'Для просмотра ссылки Войдиили Зарегистрируйся',
'Для просмотра ссылки Войдиили Зарегистрируйся',
'Для просмотра ссылки Войдиили Зарегистрируйся',
'Для просмотра ссылки Войдиили Зарегистрируйся'
]
# создадим пул работников
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. Разумеется, в Для просмотра ссылки Войди
Последнее редактирование: