Многопроцессорность в Python
В этой статье вы узнаете, как выполнять код параллельно с помощью модуля multiprocessing в Python.
Обычно программы имеют дело с двумя типами задач:
- Связанные с вводом/выводом (I/O bound). Если задача выполняет много операций ввода/вывода, она называется задачей, связанной с вводом/выводом.
Примеры: чтение из файлов, запись в файлы, подключение к базам данных и выполнение сетевого запроса.
Ускорить выполнение можно с помощью многопоточности. - Привязанные к процессору (CPU bound). Когда задача выполняет много операций с использованием процессора, она называется задачей, привязанной к процессору.
Примеры: изменение размера изображения и потоковое видео — это задачи, привязанные к процессору.
Ускорить выполнение можно с помощью многопроцессорности.
Многопроцессорность позволяет двум или более процессорам одновременно обрабатывать две или более различных частей программы. В Python для реализации мультипроцессинга используется модуль
multiprocessing.
Пример мультипроцессорной программы
import time
def task(n=100_000_000):
while n:
n -= 1
if __name__ == '__main__':
start = time.perf_counter()
task()
task()
finish = time.perf_counter()
print(f'Выполнение заняло {finish-start: .2f} секунд.')
Вывод
Выполнение заняло 12.94 секунд.
Как это работает
1. Определим функцию task()
, которая имеет большой цикл while от 10 миллионов до 0. Функция task()
привязана к процессору, потому что она выполняет вычисления.
def task(n=100_000_000):
while n:
n -= 1
2. Дважды вызовем функцию task()
и засечем время обработки:
if __name__ == '__main__':
start = time.perf_counter()
task()
task()
finish = time.perf_counter()
print(f'Выполнение заняло {finish-start: .2f} секунд.')
Используем модуль multiprocessing
В следующей программе используется модуль multiprocessing
— и она выполняется быстрее:
import time
import multiprocessing
def task(n=100_000_000):
while n:
n -= 1
if __name__ == '__main__':
start = time.perf_counter()
p1 = multiprocessing.Process(target=task)
p2 = multiprocessing.Process(target=task)
p1.start()
p2.start()
p1.join()
p2.join()
finish = time.perf_counter()
print(f'Выполнение заняло {finish-start: .2f} секунд.')
Вывод
Выполнение заняло 6.45 секунд.
Как это работает
1. Импортируем модуль multiprocessing
.
import multiprocessing
2. Создадим два процесса и передадим функцию task()
каждому из них:
p1 = multiprocessing.Process(target=task)
p2 = multiprocessing.Process(target=task)
Примечание. Конструктор
Process()
возвращает новый объект Process.
3. Вызовем метод start()
объектов Process для запуска процесса:
p1.start()
p2.start()
4. Дожидаемся завершения процессов с помощью метода join()
:
p1.join()
p2.join()
Практический пример программы с многопроцессорностью
Мы будем использовать модуль multiprocessing
для изменения размера изображений высокого разрешения.
Для начала установим библиотеку Pillow
для обработки изображений:
pip install Pillow
Теперь напишем программу, которая создает миниатюры изображений в папке images и сохраняет их в папке thumbs:
import time
import os
from PIL import Image, ImageFilter
filenames = [
'images/1.jpg',
'images/2.jpg',
'images/3.jpg',
'images/4.jpg',
'images/5.jpg',
]
def create_thumbnail(filename, size=(50,50), thumb_dir ='thumbs'):
img = Image.open(filename)
img = img.filter(ImageFilter.GaussianBlur())
img.thumbnail(size)
img.save(f'{thumb_dir}/{os.path.basename(filename)}')
print(f'Файл {filename} обработан...')
if __name__ == '__main__':
start = time.perf_counter()
for filename in filenames:
create_thumbnail(filename)
finish = time.perf_counter()
print(f'Выполнение заняло {finish-start: .2f} секунд.')
Вывод
Файл images/1.jpg обработан...
Файл images/2.jpg обработан...
Файл images/3.jpg обработан...
Файл images/4.jpg обработан...
Файл images/5.jpg обработан...
Выполнение заняло 1.28 секунд.
Теперь модифицируем программу для использования многопроцессорной обработки. Каждый процесс будет создавать миниатюру изображения:
import time
import os
import multiprocessing
from PIL import Image, ImageFilter
filenames = [
'images/1.jpg',
'images/2.jpg',
'images/3.jpg',
'images/4.jpg',
'images/5.jpg',
]
def create_thumbnail(filename, size=(50,50),thumb_dir ='thumbs'):
img = Image.open(filename)
img = img.filter(ImageFilter.GaussianBlur())
img.thumbnail(size)
img.save(f'{thumb_dir}/{os.path.basename(filename)}')
print(f'Файл {filename} обработан...')
if __name__ == '__main__':
start = time.perf_counter()
# создаем процесс
processes = [multiprocessing.Process(target=create_thumbnail, args=[filename])
for filename in filenames]
# запускаем процесс
for process in processes:
process.start()
# дожидаемся выполнение
for process in processes:
process.join()
finish = time.perf_counter()
print(f'Выполнение заняло {finish-start: .2f} секунд.')
Вывод
Файл images/5.jpg обработан...
Файл images/4.jpg обработан...
Файл images/1.jpg обработан...
Файл images/3.jpg обработан...
Файл images/2.jpg обработан...
Выполнение заняло 0.82 секунд.
При использовании многопроцессорности программа обрабатывает изображения гораздо быстрее.
Что нужно запомнить
Используйте многопроцессорную обработку Python для параллельного запуска кода для решения задач, привязанных к процессору.