← Назад к вопросам

Какие плюсы и минусы сопроцесса?

2.0 Middle🔥 151 комментариев
#Асинхронность и многопоточность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

# Плюсы и минусы сопроцессов (coroutines) в Python

Сопроцессы — это функции, которые могут приостанавливаться и возобновляться, позволяя писать асинхронный код в синхронном стиле. В Python 3.5+ это реализовано через async/await синтаксис, но важно понимать плюсы и минусы этого подхода.

Определение сопроцесса

# Сопроцесс — async функция
async def fetch_data(url):
    response = await http_client.get(url)
    return response.json()

# В отличие от обычной функции
def sync_fetch_data(url):
    response = requests.get(url)  # Блокирует
    return response.json()

Плюсы сопроцессов

1. Эффективное использование ресурсов

Проблема с потоками и процессами:

import threading
import requests

def worker():
    response = requests.get("https://api.example.com/data")
    print(response.json())

# Каждый поток требует 1-2 MB памяти
threads = [threading.Thread(target=worker) for _ in range(10000)]
# Требует ~10 GB памяти!

Решение с сопроцессами:

import asyncio
import aiohttp

async def worker():
    async with aiohttp.ClientSession() as session:
        async with session.get("https://api.example.com/data") as resp:
            return await resp.json()

# Можно запустить 10000+ сопроцессов
tasks = [worker() for _ in range(10000)]
results = asyncio.run(asyncio.gather(*tasks))
# Требует ~50 MB памяти!

Результат:

  • Потоки: 10000 потоков = 10 GB памяти
  • Сопроцессы: 10000 корутин = 50 MB памяти
  • Экономия: 200 раз меньше памяти

2. Отсутствие Race conditions

Проблема с многопоточностью:

import threading

counter = 0
lock = threading.Lock()

def increment():
    global counter
    with lock:  # Нужна блокировка!
        temp = counter
        temp += 1
        counter = temp

threads = [threading.Thread(target=increment) for _ in range(100)]
for t in threads:
    t.start()
for t in threads:
    t.join()

print(counter)  # Может быть не 100 без блокировки!

Решение с сопроцессами:

import asyncio

counter = 0

async def increment():
    global counter
    temp = counter  # Нет race condition!
    temp += 1
    counter = temp

async def main():
    tasks = [increment() for _ in range(100)]
    await asyncio.gather(*tasks)
    print(counter)  # Всегда 100

asyncio.run(main())

Почему? Сопроцессы переключаются только в точках await, не в случайный момент.

3. Более простой код

С потоками:

import threading
import queue

results_queue = queue.Queue()
errors_queue = queue.Queue()

def fetch_with_threads(urls):
    def worker(url):
        try:
            result = requests.get(url).json()
            results_queue.put((url, result))
        except Exception as e:
            errors_queue.put((url, e))
    
    threads = [threading.Thread(target=worker, args=(url,)) for url in urls]
    for t in threads:
        t.start()
    for t in threads:
        t.join()
    
    results = {}
    while not results_queue.empty():
        url, result = results_queue.get()
        results[url] = result
    
    return results

С сопроцессами:

import asyncio
import aiohttp

async def fetch_with_async(urls):
    async def fetch_one(session, url):
        async with session.get(url) as resp:
            return url, await resp.json()
    
    async with aiohttp.ClientSession() as session:
        tasks = [fetch_one(session, url) for url in urls]
        results = await asyncio.gather(*tasks, return_exceptions=True)
        return dict(results)

results = asyncio.run(fetch_with_async(urls))

Код с async/await более читаемый и близок к синхронному.

4. Легче отладить

Потоки:

import threading
import time

def problematic_function():
    time.sleep(1)  # Блокирует
    print("Done")  # Никогда не вызовется если есть deadlock

t = threading.Thread(target=problematic_function)
t.start()
t.join(timeout=2)  # Нужен timeout

if t.is_alive():
    print("Deadlock detected!")  # Как починить?

Сопроцессы:

import asyncio

async def my_coroutine():
    await asyncio.sleep(1)  # Не блокирует
    print("Done")

async def main():
    try:
        await asyncio.wait_for(my_coroutine(), timeout=2)
    except asyncio.TimeoutError:
        print("Timeout!")

asyncio.run(main())

Ошибки более предсказуемы.

Минусы сопроцессов

1. Кривая обучения

Требует понимания:

# Нужно знать разницу:
await fetch()  # Ждём результата (блокируем корутину)
asyncio.create_task(fetch())  # Запустили параллельно, но не ждём

# Нужно знать, что это неправильно:
async def wrong():
    for i in range(10):
        result = requests.get(url)  # БЛОКИРУЕТ ВСЕ!
        print(result)

# Правильно так:
async def correct():
    for i in range(10):
        async with aiohttp.ClientSession() as session:
            async with session.get(url) as resp:
                print(await resp.json())

2. Экосистема не всегда готова

Проблема:

import asyncio
import requests  # Синхронная библиотека!

async def bad_code():
    result = requests.get(url)  # БЛОКИРУЕТ весь event loop!
    return result

# Нужна асинхронная альтернатива
import aiohttp

async def good_code():
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as resp:
            return await resp.json()

Несовместимые библиотеки:

  • requests → aiohttp, httpx
  • psycopg2 → asyncpg, psycopg3[binary]
  • SQLAlchemy sync → SQLAlchemy async
  • Redis → aioredis

3. Запутанная отладка при плохом коде

Проблема:

async def fetch_data():
    await asyncio.sleep(1)  # Выглядит быстро
    # Но если это вложено 10 раз без параллелизма:
    
async def main():
    result1 = await fetch_data()  # 1 сек
    result2 = await fetch_data()  # 1 сек
    result3 = await fetch_data()  # 1 сек
    # Итого: 3 секунды (работает как синхронный код!)

# Нужно запускать параллельно:
async def main():
    results = await asyncio.gather(
        fetch_data(),  # 1 сек
        fetch_data(),  # параллельно
        fetch_data(),  # параллельно
    )
    # Итого: 1 секунда

4. Нельзя использовать обычный CPU-bound код

Проблема:

async def cpu_bound():
    # Это БЛОКИРУЕТ весь event loop!
    result = sum(range(10**9))
    return result

async def main():
    # Даже другие задачи будут заморозиться
    result = await cpu_bound()
    print(result)

Решение:

import asyncio
from concurrent.futures import ProcessPoolExecutor

async def cpu_bound():
    loop = asyncio.get_event_loop()
    # Запустить в отдельном процессе
    result = await loop.run_in_executor(
        ProcessPoolExecutor(),
        lambda: sum(range(10**9))
    )
    return result

5. Зависимости сложнее управлять

Проблема:

class Repository:
    def get_user(self, id):
        # Синхронный метод
        return db.query(User).get(id)

class AsyncService:
    def __init__(self, repo: Repository):
        self.repo = repo
    
    async def get_user(self, id):
        # Как использовать синхронный repo в async методе?
        # Только через run_in_executor!
        return await asyncio.get_event_loop().run_in_executor(
            None, self.repo.get_user, id
        )

6. Отладка более сложная при исключениях

Проблема:

async def failing_task():
    await asyncio.sleep(0.1)
    raise ValueError("Something went wrong")

async def main():
    # Исключение проглатывается!
    task = asyncio.create_task(failing_task())
    # Продолжаем выполнение
    
    # Исключение может возникнуть позже
    await asyncio.sleep(1)
    
    # И только здесь падает
    await task

# Traceback будет запутанным

Когда использовать

Используй async/await когда:

  • Много I/O операций (API запросы, БД запросы)
  • Нужна высокая параллелизм (1000+ соединений)
  • Система должна обрабатывать много одновременных запросов
# Идеально для:
# - Web API (FastAPI, aiohttp)
# - Чтение/запись в БД
# - Параллельные HTTP запросы
# - WebSocket соединения

Не используй async/await когда:

  • Много CPU-bound операций (вычисления, обработка данных)
  • Нужна простота (один пользователь)
  • Синхронная логика
# Используй обычный код для:
# - Обработки изображений
# - Математических вычислений
# - Парсинга больших файлов
# - Простых скриптов

Практический пример: выбор подхода

# Сценарий: Загрузка 100 URL-адресов

# ПЛОХО: Синхронно (100+ секунд)
for url in urls:
    response = requests.get(url)  # 1 сек на запрос
    process(response)

# ХОРОШО: Асинхронно (1 секунда с async)
async def download_all():
    async with aiohttp.ClientSession() as session:
        tasks = [fetch(session, url) for url in urls]
        results = await asyncio.gather(*tasks)
        for result in results:
            process(result)

# ХОРОШО: Потоки если нет async библиотеки
from concurrent.futures import ThreadPoolExecutor

with ThreadPoolExecutor(max_workers=10) as executor:
    futures = [executor.submit(fetch, url) for url in urls]
    for future in futures:
        process(future.result())

Заключение

Сопроцессы (async/await) — это мощный инструмент для:

  • Масштабируемых I/O-bound приложений
  • Высокоэффективного использования ресурсов
  • Предсказуемого параллелизма без race conditions

Но требуют:

  • Понимания асинхронной модели
  • Асинхронных версий библиотек
  • Внимательности при комбинировании с синхронным кодом

Для новых проектов, требующих высокого параллелизма, async/await в Python 3.5+ — это стандартный выбор.

Какие плюсы и минусы сопроцесса? | PrepBro