Стоит ли использовать многопроцессорность (multiprocessing) в современных проектах?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Многопроцессорность в современных проектах Python
Контекст: GIL и параллелизм
Глобальная блокировка интерпретатора (Global Interpreter Lock, GIL) — это одна из основных особенностей CPython. GIL позволяет работать только одному потоку с bytecode интерпретатора одновременно, что делает threading неэффективным для вычислительно-интенсивных задач.
Multiprocessing создаёт отдельные процессы с собственными интерпретаторами Python, каждый со своим GIL, что позволяет добиться настоящего параллелизма на многоядерных системах.
Когда использовать multiprocessing
1. CPU-bound операции (вычисления)
Если задача требует интенсивных вычислений, multiprocessing — лучший выбор:
from multiprocessing import Pool
import time
def cpu_intensive_task(n):
# Вычисление факториала (CPU-bound)
result = 1
for i in range(2, n):
result *= i
return result
if __name__ == __main__:
with Pool(4) as p:
results = p.map(cpu_intensive_task, [1000, 2000, 3000, 4000])
print(results)
2. Изоляция критических ошибок
Если процесс падает, он не влияет на другие:
from multiprocessing import Process
def worker(queue):
try:
result = 1 / 0 # Ошибка
except Exception as e:
queue.put(f"Error: {e}")
if __name__ == __main__:
from queue import Queue
q = Queue()
p = Process(target=worker, args=(q,))
p.start()
p.join()
print(q.get()) # Error: division by zero
Когда НЕ использовать multiprocessing
1. I/O-bound операции (сеть, файлы)
Для I/O используй asyncio или threading — они дешевле по памяти:
# Плохо: multiprocessing для I/O
from multiprocessing import Pool
import requests
def fetch(url):
return requests.get(url).status_code
# Хорошо: asyncio
import asyncio
import aiohttp
async def fetch(session, url):
async with session.get(url) as resp:
return resp.status
async def main():
async with aiohttp.ClientSession() as session:
tasks = [fetch(session, url) for url in urls]
await asyncio.gather(*tasks)
asyncio.run(main())
2. Частая передача данных между процессами
Serialization (pickling) — дорогая операция:
from multiprocessing import Queue
import time
# Неэффективно: много serialization
q = Queue()
for i in range(100000):
q.put({"data": list(range(1000))}) # Pickling!
item = q.get()
Современные альтернативы (Python 3.13+)
Отмена GIL в CPython 3.13
NO_GIL режим (экспериментальный) позволяет использовать threading для CPU-bound задач:
import threading
def cpu_task(n):
result = 1
for i in range(2, n):
result *= i
return result
threads = [
threading.Thread(target=cpu_task, args=(1000,))
for _ in range(4)
]
for t in threads:
t.start()
for t in threads:
t.join()
# В Python 3.13+ это будет эффективно без GIL
Практический пример: Hybrid подход
from multiprocessing import Pool
import asyncio
# CPU-bound работа в отдельном процессе
def heavy_computation(n):
return sum(i**2 for i in range(n))
# I/O-bound работа асинхронно
async def fetch_data(url, executor):
loop = asyncio.get_event_loop()
result = await loop.run_in_executor(executor, requests.get, url)
return result.status_code
async def main():
# CPU tasks через multiprocessing
with Pool(2) as pool:
cpu_results = pool.map(heavy_computation, [1000000, 2000000])
# I/O tasks через asyncio
from concurrent.futures import ThreadPoolExecutor
with ThreadPoolExecutor() as executor:
io_results = await asyncio.gather(
fetch_data("https://example.com", executor),
fetch_data("https://python.org", executor)
)
return cpu_results, io_results
if __name__ == "__main__":
asyncio.run(main())
Выводы
- Используй multiprocessing для CPU-bound задач и критических вычислений
- Избегай для I/O (используй asyncio) и частого обмена данными
- Мониторь память — каждый процесс требует ~30-50 МБ базовой памяти
- Рассмотри asyncio перед multiprocessing — часто проще и эффективнее
- Python 3.13+ меняет ситуацию с GIL — threading может стать конкурентным