Заменял ли мультипроцессинг на асинхронность
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Замена мультипроцессинга на асинхронность
Различие в применении
Мультипроцессинг и асинхронность решают разные задачи, хотя обе могут выполнять операции параллельно.
Мультипроцессинг:
- Для задач, требующих реального параллелизма (CPU-bound)
- Обходит GIL (Global Interpreter Lock) в Python
- Использует отдельные процессы с независимыми интерпретаторами
- Большие накладные расходы на создание процессов
Асинхронность:
- Для задач с ожиданием (I/O-bound)
- Выполняется в одном процессе, одном потоке
- Быстрое переключение между задачами
- Минимальные накладные расходы
Когда я бы заменил мультипроцессинг на асинхронность
Пример 1: Обработка сетевых запросов
Исходно на мультипроцессинге (неэффективно):
from multiprocessing import Pool
import requests
import time
def fetch_url(url):
response = requests.get(url, timeout=5)
return len(response.text)
if __name__ == '__main__':
urls = ['https://httpbin.org/delay/2'] * 10
start = time.time()
with Pool(processes=5) as pool:
results = pool.map(fetch_url, urls)
print(f"Время (мультипроцессинг): {time.time() - start:.1f} сек") # ~4 сек
Замена на асинхронность (эффективно):
import asyncio
import aiohttp
import time
async def fetch_url(session, url):
async with session.get(url, timeout=5) as response:
return len(await response.text())
async def main():
urls = ['https://httpbin.org/delay/2'] * 10
start = time.time()
async with aiohttp.ClientSession() as session:
tasks = [fetch_url(session, url) for url in urls]
results = await asyncio.gather(*tasks)
print(f"Время (асинхронность): {time.time() - start:.1f} сек") # ~2 сек
asyncio.run(main())
Асинхронность быстрее на 50% и использует меньше памяти.
Пример 2: Парсинг HTML с сохранением в БД
Исходно на мультипроцессинге:
from multiprocessing import Pool
from bs4 import BeautifulSoup
import sqlite3
import requests
def parse_page(url):
response = requests.get(url)
soup = BeautifulSoup(response.text, 'html.parser')
# Проблема: каждый процесс создаёт свое подключение к БД
conn = sqlite3.connect('data.db')
cursor = conn.cursor()
# ... парсинг и сохранение ...
conn.close()
return len(soup.find_all('a'))
if __name__ == '__main__':
urls = ['https://example.com/page1', 'https://example.com/page2']
with Pool(processes=4) as pool:
results = pool.map(parse_page, urls)
Замена на асинхронность:
import asyncio
import aiohttp
from bs4 import BeautifulSoup
from sqlalchemy import create_engine
from sqlalchemy.orm import Session
# Одно подключение к БД для всех async задач
engine = create_engine('sqlite:///data.db')
async def parse_page(session, url):
async with session.get(url) as response:
text = await response.text()
soup = BeautifulSoup(text, 'html.parser')
# Используем единый engine для всех задач
with Session(engine) as db:
# ... парсинг и сохранение ...
links_count = len(soup.find_all('a'))
return links_count
async def main():
urls = ['https://example.com/page1', 'https://example.com/page2']
async with aiohttp.ClientSession() as session:
tasks = [parse_page(session, url) for url in urls]
results = await asyncio.gather(*tasks)
asyncio.run(main())
Преимущества:
- Одно подключение к БД вместо multiple connections
- Проще управление ресурсами
- Лучше производительность
Пример 3: Telegram Bot с асинхронными обработчиками
Вместо использования мультипроцессинга для обработки нескольких пользователей одновременно, используется асинхронность:
from aiogram import Router, F
from aiogram.types import Message
import asyncio
router = Router()
@router.message(F.text == '/start')
async def start_handler(message: Message):
# Может быть множество пользователей одновременно
await asyncio.sleep(0.5) # Имитация I/O операции
await message.answer("Привет!")
@router.message()
async def any_message_handler(message: Message):
# Все обработчики выполняются асинхронно
# Диспетчер singlehandler сам управляет event loop
await message.answer(f"Вы написали: {message.text}")
Аiogram использует асинхронность для обработки множества пользователей без блокировок.
Когда НЕ заменять мультипроцессинг
Остаются случаи, когда нужен мультипроцессинг:
1. CPU-bound операции:
from multiprocessing import Pool
import math
def compute_prime_count(n):
"""Чистое вычисление — требует реального параллелизма"""
count = 0
for i in range(2, n):
if all(i % j != 0 for j in range(2, int(math.sqrt(i)) + 1)):
count += 1
return count
if __name__ == '__main__':
numbers = [100000, 100000, 100000, 100000]
with Pool(processes=4) as pool:
results = pool.map(compute_prime_count, numbers)
2. Обработка больших файлов:
from multiprocessing import Pool
def process_chunk(chunk):
"""Обработка части файла в отдельном процессе"""
return sum(int(line.strip()) for line in chunk if line.strip())
if __name__ == '__main__':
# Разделяем большой файл на chunks
# Обрабатываем параллельно
with Pool(processes=4) as pool:
results = pool.map(process_chunk, chunks)
Матрица решений
| Тип задачи | Лучший выбор | Почему |
|---|---|---|
| I/O операции (сеть, файлы, БД) | Асинхронность | Без GIL, быстро, мало памяти |
| CPU-bound вычисления | Мультипроцессинг | Обходит GIL, настоящий параллелизм |
| Простые скрипты | Синхронный код | Проще, понятнее, достаточно |
| Веб-приложение | Асинхронность (FastAPI) | Сотни одновременных запросов |
| Обработка данных | Зависит от типа | CPU → мультипроцессинг, I/O → асинхронность |
Практический совет
В 90% случаев при работе с I/O операциями я заменяю мультипроцессинг на асинхронность. Это дает:
✅ Быстрее (2-5x) ✅ Меньше памяти ✅ Проще отладка ✅ Меньше синхронизации
Мультипроцессинг оставляю только для CPU-bound задач, где нужен настоящий параллелизм.