Работа с процессами: multiprocessing и параллелизм

Почему процессы? 🏎️💨

Когда твой код упирается в ограничения одного CPU-ядра, а задачи можно выполнять независимо — процессы спешат на помощь! В отличие от потоков (threading), процессы используют несколько ядер CPU и полностью изолированы друг от друга.

Пример-мотивация:

# Обычная синхронная функция
def calculate_squares(numbers):
    return [n * n for n in numbers]

# Представь, что numbers содержит 10_000_000 элементов...

Основы multiprocessing 🛠️

Модуль multiprocessing в Python создаёт новые интерпретаторы Python как отдельные процессы. Вот базовый сценарий:

from multiprocessing import Process
import os

def worker(name):
    print(f'Процесс {name} (PID: {os.getpid()}) выполняет работу')

if __name__ == '__main__':
    processes = []
    for i in range(3):
        p = Process(target=worker, args=(f'Worker-{i}',))
        processes.append(p)
        p.start()

    for p in processes:
        p.join()  # Ждём завершения всех процессов

💡 Главное правило — код процессов должен быть внутри if __name__ == '__main__'! Иначе получим рекурсивное создание процессов.


Обмен данными между процессами 📦

Процессы не имеют общей памяти, но есть несколько способов обмена данными:

1. Очереди (Queue)

from multiprocessing import Queue, Process

def producer(q):
    q.put('Данные от процесса')

def consumer(q):
    print(f'Получено: {q.get()}')

if __name__ == '__main__':
    q = Queue()
    p1 = Process(target=producer, args=(q,))
    p2 = Process(target=consumer, args=(q,))
    p1.start(); p2.start()
    p1.join(); p2.join()

2. Общая память (Value, Array)

from multiprocessing import Process, Value, Array

def modify(n, arr):
    n.value = 3.1415
    for i in range(len(arr)):
        arr[i] *= 2

if __name__ == '__main__':
    num = Value('d', 0.0)  # 'd' — double
    arr = Array('i', range(5))  # 'i' — integer

    p = Process(target=modify, args=(num, arr))
    p.start()
    p.join()

    print(num.value)  # 3.1415
    print(arr[:])     # [0, 2, 4, 6, 8]

Практический кейс: обработка изображений 🖼️

Допустим, у нас есть папка с изображениями, которые нужно преобразовать:

from multiprocessing import Pool
import cv2, os

def process_image(img_path):
    img = cv2.imread(img_path)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    cv2.imwrite(f'processed_{img_path}', gray)
    return img_path

if __name__ == '__main__':
    images = ['img1.jpg', 'img2.jpg', 'img3.jpg']  # Предположим, файлы существуют

    with Pool(processes=os.cpu_count()) as pool:
        results = pool.map(process_image, images)

    print(f'Обработано: {len(results)} изображений')

🔥 Pool автоматически распределяет задачи по доступным ядрам CPU!


Когда НЕ использовать процессы? ⚠️

  1. Задачи с общей памятью — процессы не могут легко делиться сложными объектами Python.
  2. Частая коммуникация — обмен данными между процессами дороже, чем между потоками.
  3. Очень лёгкие задачи — накладные расходы на создание процесса могут превысить выгоду.

Pro-советы от Данилы Бежина 🧙‍♂️

  1. Всегда закрывайте процессы с помощью join() или контекстного менеджера.
  2. Для CPU-задач процессы почти всегда лучше потоков (GIL не мешает!).
  3. Используйте multiprocessing.Pool для простых сценариев "разделяй и властвуй".

👉 Больше реальных примеров смотри в видео Данилы Бежина!

Скрыть рекламу навсегда

🌱 Индвидидулаьные занятия

Индивидуальные онлайн-занятия по программированию для детей и подростков

Личный подход, без воды, с фокусом на понимание и реальные проекты.

🚀 Записаться на занятие