Multiprocessing: процессы для CPU-задач
Если потоки не ускоряют счёт из-за GIL — запустим несколько процессов, у каждого свой GIL.
multiprocessing — модуль Python, запускающий задачи в отдельных процессах, у каждого свой интерпретатор и свой GIL, что даёт настоящий параллелизм на нескольких ядрах.
GIL блокирует параллелизм потоков внутри одного процесса. Но он не мешает разным процессам! Каждый процесс — отдельный интерпретатор Python со своим GIL. Запустив 4 процесса на 4 ядрах, вы получите 4-кратное ускорение для CPU-задач.
Базовый пример
from multiprocessing import Pool
def heavy(n):
return sum(i * i for i in range(n))
if __name__ == "__main__":
with Pool(4) as p: # 4 процесса на 4 ядра
results = p.map(heavy, [10**6, 10**6, 10**6, 10**6])
print(sum(results))Обратите внимание на if __name__ == "__main__" — без него на некоторых ОС запуск процессов уйдёт в бесконечную рекурсию.
Цена параллелизма процессов
За настоящий параллелизм платят. У процессов раздельная память, поэтому аргументы и результаты приходится сериализовать (pickle) и пересылать между процессами. Для мелких задач эти накладные расходы могут съесть весь выигрыш — процессы выгодны на по-настоящему тяжёлых вычислениях.
# смоделируем выигрыш: 4 задачи по 2 сек на 4 ядрах
task_time = 2.0
tasks = 4
cores = 4
sequential = task_time * tasks
parallel = task_time * (tasks / cores) # все 4 разом
print("последовательно:", sequential, "сек")
print("4 процесса:", parallel, "сек")Вывод:
последовательно: 8.0 сек 4 процесса: 2.0 сек
Когда что выбирать
| Нагрузка | Инструмент | Почему |
| Тяжёлый счёт (CPU-bound) | multiprocessing | обходит GIL, реальный параллелизм |
| Ожидание сети/диска (I/O-bound) | threading / asyncio | GIL отпускается, потоки дешевле процессов |
Как работает под капотом
При старте процесса ОС создаёт отдельное адресное пространство (через fork или spawn). Данные между процессами идут через каналы (pipes) и очереди, и всё, что пересылается, должно поддерживать pickle. Поэтому, например, нельзя передать в процесс лямбду или открытый файловый дескриптор. Pool переиспользует фиксированное число процессов, раздавая им задачи, — это избавляет от затрат на постоянное создание новых.
Частые ошибки
- Забыть
if __name__ == "__main__". На Windows/macOS соspawnэто приводит к рекурсивному запуску. - Гонять через процессы мелочёвку. Сериализация и пересылка съедят выигрыш — процессы для тяжёлых задач.
- Ждать общую память «как у потоков». У процессов память раздельная; общий доступ требует особых средств (
Value,shared_memory).
Итог
- У каждого процесса свой GIL — отсюда настоящий параллелизм.
multiprocessing— выбор для CPU-bound задач.- Данные между процессами сериализуются (pickle), это стоит времени.
- Для мелких задач накладные расходы могут перевесить выигрыш.