Многопроцессорное программирование: параллелизм в действии
Почему многопроцессорность? 🚀
Когда ваш скрипт работает медленно, особенно при обработке больших данных или выполнении CPU-bound задач (например, математических вычислений), обычные потоки в Python не помогут из-за GIL (Global Interpreter Lock). Здесь на помощь приходит многопроцессорность — настоящий суперсила Python для параллельных вычислений!
📌 Ключевое отличие от многопоточности:
- Процессы выполняются параллельно (используют разные ядра CPU).
- Потоки выполняются псевдопараллельно (из-за GIL).
Основы модуля multiprocessing 🔧
Стандартная библиотека Python предлагает мощный модуль multiprocessing. Рассмотрим его базовые компоненты:
1. Простой процесс
Создадим и запустим функцию в отдельном процессе:
import multiprocessing
def print_numbers():
for i in range(5):
print(f"Число: {i}")
if __name__ == "__main__":
process = multiprocessing.Process(target=print_numbers)
process.start() # Запускаем процесс
process.join() # Ждём завершения
🔹 Что важно:
- if __name__ == "__main__": — это обязательно для Windows и MacOS (без этого код может вести себя странно).
- .join() — блокирует основной поток, пока процесс не завершится.
2. Пул процессов 🏊
Если задач много, лучше использовать пул процессов — это группа заранее созданных воркеров, которые распределяют задачи между собой.
import multiprocessing
def square(x):
return x * x
if __name__ == "__main__":
with multiprocessing.Pool(processes=4) as pool: # 4 процесса
results = pool.map(square, [1, 2, 3, 4, 5])
print(results) # [1, 4, 9, 16, 25]
📌 Почему это круто?
- Автоматическое распределение задач.
- Эффективное использование CPU (особенно на многоядерных системах).
Обмен данными между процессами 🔄
Процессы не разделяют память (в отличие от потоков). Для обмена данными используются:
1. Очереди (Queue)
Позволяют передавать данные между процессами.
import multiprocessing
def worker(queue):
queue.put("Привет из дочернего процесса!")
if __name__ == "__main__":
queue = multiprocessing.Queue()
p = multiprocessing.Process(target=worker, args=(queue,))
p.start()
print(queue.get()) # Получаем сообщение
p.join()
2. Общая память (Value, Array)
Для числовых данных можно использовать разделяемые переменные.
import multiprocessing
def increment(shared_value):
shared_value.value += 1
if __name__ == "__main__":
value = multiprocessing.Value('i', 0) # 'i' — тип int
processes = [multiprocessing.Process(target=increment, args=(value,))
for _ in range(5)]
for p in processes:
p.start()
for p in processes:
p.join()
print(value.value) # Может быть от 1 до 5 (гонка данных!)
⚠️ Важно: Без синхронизации возможна гонка данных (race condition). Для защиты используйте блокировки (Lock).
Когда использовать многопроцессорность? 🎯
- CPU-bound задачи (тяжёлые вычисления, ML, обработка изображений).
- Параллельная обработка данных (например, загрузка и анализ нескольких файлов).
- Когда GIL мешает (в отличие от потоков, процессы независимы).
🚀 Пример из жизни:
Если ваш код обрабатывает 1000 изображений, разбейте их на 4 группы и запустите 4 процесса — ускорение может быть почти в 4 раза (если CPU 4-ядерный)!
Заключение
Многопроцессорность в Python — это мощный инструмент для реального параллелизма. Главное:
- Используйте multiprocessing для CPU-bound задач.
- Помните про Queue, Value, Lock для обмена данными.
- Оптимизируйте количество процессов (обычно Pool(processes=multiprocessing.cpu_count())).
🔥 Для более глубокого погружения смотрите разборы Данилы Бежина на YouTube: https://www.youtube.com/@DanilaBezhin. Вперёд, к параллельным вычислениям!