Короткий ответ
Процессы — независимые экземпляры интерпретатора с собственной памятью и своим GIL, что даёт реальный параллелизм на нескольких ядрах; потоки работают внутри одного процесса, делят общую память и один GIL, что в CPython ограничивает параллелизм для CPU-bound кода.
Как это работает подробнее
multiprocessing создаёт отдельные ОС-процессы с собственным адресным пространством памяти — обмен данными между ними требует межпроцессного взаимодействия (pickle, очереди). threading создаёт потоки в рамках одного процесса с общей памятью, что упрощает обмен данными, но ограничено одним GIL в CPython.
Пример кода
example-251.py
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
import time
def io_task():
time.sleep(1) # имитация сетевого запроса
def cpu_task(n):
return sum(i * i for i in range(n))
# Для io_task ThreadPoolExecutor эффективно перекрывает ожидания.
# Для cpu_task ProcessPoolExecutor даёт реальное ускорение на многоядерной машине,
# а ThreadPoolExecutor почти не ускорит выполнение из-за GILЧто использовать на практике
- Практика. Для CPU-bound нагрузки процессы дают реальное распараллеливание на несколько ядер, тогда как потоки для этой задачи почти бесполезны в CPython. Для I/O-bound нагрузки потоки — лёгкий способ параллельно ждать несколько операций, так как GIL освобождается во время блокирующего I/O.
Подводные камни
- Риски. Используют
multiprocessingдля простого I/O-bound параллелизма, получая лишние издержки на создание процессов и сериализацию; используютthreadingдля тяжёлых вычислений, ожидая ускорения, которого не будет из-за GIL.