Ответ
Выбор зависит от характера задачи, а именно от того, является ли она CPU-bound (ограничена производительностью процессора) или I/O-bound (ограничена скоростью ввода-вывода).
Задача по обработке изображений состоит из двух частей:
- Чтение/запись файлов: I/O-bound операция.
- Изменение размера изображения: CPU-bound операция.
Проанализируем подходы с учётом GIL (Global Interpreter Lock) в CPython, который не позволяет нескольким потокам выполнять Python-байткод одновременно на разных ядрах CPU.
- Многопоточность (
threading
): Эффективна для I/O-bound задач, так как потоки могут переключаться во время ожидания операций ввода-вывода. Однако из-за GIL она не ускоряет CPU-bound вычисления. - Асинхронность (
asyncio
): Идеальна для большого количества I/O-операций (например, сетевых запросов). Работает в одном потоке, поэтому не подходит для CPU-bound задач. - Мультипроцессинг (
multiprocessing
): Создаёт отдельные процессы, каждый со своим интерпретатором Python и GIL. Это единственный способ в CPython по-настоящему распараллелить CPU-bound задачи на несколько ядер.
Вывод: Поскольку изменение размера изображений — это ресурсоёмкая CPU-bound операция, мультипроцессинг является лучшим выбором для этой задачи. Он позволит эффективно использовать все ядра процессора.
Пример с ProcessPoolExecutor
:
from concurrent.futures import ProcessPoolExecutor
import os
from PIL import Image
INPUT_DIR = 'input_images'
OUTPUT_DIR = 'output_images'
def resize_image(filename):
"""Читает изображение, уменьшает его и сохраняет в другую папку."""
try:
input_path = os.path.join(INPUT_DIR, filename)
output_path = os.path.join(OUTPUT_DIR, filename)
with Image.open(input_path) as img:
img.thumbnail((800, 800)) # Уменьшаем до 800x800
img.save(output_path)
return f'{filename} успешно обработан.'
except Exception as e:
return f'Ошибка при обработке {filename}: {e}'
if __name__ == '__main__':
os.makedirs(OUTPUT_DIR, exist_ok=True)
files = [f for f in os.listdir(INPUT_DIR) if f.endswith(('jpg', 'png'))]
# Используем пул процессов для параллельной обработки
with ProcessPoolExecutor(max_workers=os.cpu_count()) as executor:
results = executor.map(resize_image, files)
for result in results:
print(result)
Краткое правило:
- I/O-bound (сеть, диск):
asyncio
илиthreading
. - CPU-bound (вычисления, обработка данных):
multiprocessing
.