Многопроцессорность в 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 для параллельного запуска кода для решения задач, привязанных к процессору.

codechick

СodeСhick.io - простой и эффективный способ изучения программирования.

2024 ©