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

Почему не стоит в event loop много задач?

2.0 Middle🔥 141 комментариев
#Python Core

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

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

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

Почему не стоит загружать event loop большим количеством задач

Event loop в асинхронном Python — это сердце приложения. Его производительность и отзывчивость критичны. Избыток задач приводит к проблемам с производительностью, задержками и даже отказам в обслуживании.

1. Event Loop — это однопоточный обработчик

Event loop выполняет все задачи последовательно в одном потоке. Это не параллелизм!

import asyncio

async def task_a():
    print("Задача A начало")
    await asyncio.sleep(1)
    print("Задача A конец")

async def task_b():
    print("Задача B начало")
    await asyncio.sleep(1)
    print("Задача B конец")

async def main():
    # Эти задачи выполняются ПАРАЛЛЕЛЬНО (сложены в очередь)
    await asyncio.gather(task_a(), task_b())

asyncio.run(main())

# Вывод:
# Задача A начало
# Задача B начало
# (обе ждут 1 секунду ОДНОВРЕМЕННО в одном потоке)
# Задача A конец
# Задача B конец
# Общее время: ~1 сек, а не 2

Ключевой момент: Event loop может переключаться между задачами во время ожидания (I/O), но выполняет только одну задачу в один момент времени.

2. Блокирующий код замораживает весь event loop

Если одна задача выполняет синхронный блокирующий код, весь event loop замораживается.

import asyncio
import time

async def slow_task():
    """Плохо: синхронный код блокирует event loop"""
    print("Начало медленной задачи")
    time.sleep(5)  # ❌ БЛОКИРУЕТ event loop на 5 секунд!
    print("Конец медленной задачи")
    return "результат"

async def fast_task():
    """Эта задача заморозится на 5 секунд"""
    print("Начало быстрой задачи")
    await asyncio.sleep(0.1)
    print("Конец быстрой задачи")

async def main():
    # Обе задачи стартуют, но slow_task блокирует event loop
    tasks = [slow_task(), fast_task()]
    await asyncio.gather(*tasks)

start = time.time()
asyncio.run(main())
print(f"Время: {time.time() - start:.1f} сек")

# Вывод:
# Начало медленной задачи
# Конец медленной задачи (5 сек)
# Начало быстрой задачи (только ПОСЛЕ slow_task)
# Конец быстрой задачи
# Время: 5.1 сек

Почему это плохо: Fast_task должна была выполниться за 0.1 сек, но ждала 5 сек!

3. CPU-bound задачи замораживают event loop

Любой вычислительно-интенсивный код замораживает event loop.

import asyncio

async def cpu_task():
    """Вычисление без await — замораживает event loop"""
    print("Начало вычисления")
    
    # Тяжёлые вычисления
    result = sum(i**2 for i in range(100_000_000))  # ~1 сек
    
    print("Конец вычисления")
    return result

async def ping():
    """Простая асинхронная задача"""
    for i in range(5):
        print(f"Ping {i}")
        await asyncio.sleep(0.1)

async def main():
    # cpu_task блокирует ping
    await asyncio.gather(cpu_task(), ping())

start = asyncio.get_event_loop().time()
asyncio.run(main())

# Вывод:
# Начало вычисления
# Конец вычисления (1 сек, ping не может выполниться)
# Ping 0
# Ping 1
# Ping 2
# Ping 3
# Ping 4

4. Проблема с большим количеством задач в очереди

От количества задач не зависит прямо, но их характер критичен.

import asyncio

async def task(i):
    """Каждая задача ждёт I/O"""
    await asyncio.sleep(1)
    return f"Task {i} done"

async def main():
    # Создаём 10000 задач
    tasks = [task(i) for i in range(10000)]
    
    # Event loop обрабатывает их волнами
    results = await asyncio.gather(*tasks)
    print(f"Завершено: {len(results)} задач")

import time
start = time.time()
asyncio.run(main())
print(f"Время: {time.time() - start:.1f} сек")

# Время: ~1.0 сек
# ✅ Event loop ПАРАЛЛЕЛИТ I/O задачи, это ОК

5. Реальная проблема: Задержки в обработке

import asyncio
import time

async def slow_blocking_task():
    """Задача с блокирующим кодом"""
    print(f"{time.time():.2f}: Начало блокирующей задачи")
    time.sleep(2)  # ❌ ЗАМОРАЖИВАЕТ event loop
    print(f"{time.time():.2f}: Конец блокирующей задачи")

async def critical_task():
    """Критичная для ответа задача"""
    print(f"{time.time():.2f}: Критичная задача ждёт")
    await asyncio.sleep(0.1)  # Должна выполниться быстро
    print(f"{time.time():.2f}: Критичная задача готова ответить пользователю")

async def main():
    # Оба запускаются, но блокирующая задача замораживает event loop
    await asyncio.gather(slow_blocking_task(), critical_task())

start = time.time()
asyncio.run(main())
print(f"Общее время: {time.time() - start:.1f} сек")

# Вывод:
# 0.00: Начало блокирующей задачи
# 0.00: Критичная задача ждёт
# 2.00: Конец блокирующей задачи
# 2.10: Критичная задача готова ответить пользователю
# Общее время: 2.1 сек

# ПРОБЛЕМА: Пользователь ждёт 2.1 сек вместо 0.1 сек!

6. Плохие практики

❌ Много синхронного кода в асинхронных функциях

async def bad_handler(request):
    # Синхронное подключение к БД
    user = db.query(User).filter(User.id == request.user_id).first()  # БЛОКИРУЕТ!
    
    # Синхронный HTTP запрос
    response = requests.get("https://api.example.com/data")  # БЛОКИРУЕТ!
    
    return user

❌ Много CPU-bound вычислений

async def bad_calculation():
    # Тяжёлые вычисления
    for i in range(1_000_000_000):
        x = i ** 2  # ЗАМОРАЖИВАЕТ event loop!
    return x

❌ Event loop перегружена задачами со слабым приоритизацией

# Создаём 100 000 задач, mix of CPU и I/O
tasks = [
    task_1(),  # CPU-bound (2 сек)
    task_2(),  # I/O (5 сек)
    task_3(),  # CPU-bound (3 сек)
    # ... и ещё 99 997
]
await asyncio.gather(*tasks)  # Event loop парализована

7. Хорошие практики

✅ Используй асинхронные библиотеки

import asyncio
import aiohttp

async def good_handler(request):
    # Асинхронное подключение к БД
    user = await db.query(User).get(request.user_id)
    
    # Асинхронный HTTP запрос
    async with aiohttp.ClientSession() as session:
        async with session.get("https://api.example.com/data") as resp:
            data = await resp.json()
    
    return user

✅ Перемещай CPU задачи в другой поток

import asyncio

def cpu_intensive():
    """Синхронная функция с тяжелыми вычислениями"""
    return sum(i**2 for i in range(100_000_000))

async def good_computation():
    # Запусти в отдельном потоке, не блокируя event loop
    loop = asyncio.get_event_loop()
    result = await loop.run_in_executor(None, cpu_intensive)
    return result

✅ Используй семафоры для ограничения параллельных задач

import asyncio

async def limited_task(semaphore, i):
    async with semaphore:
        print(f"Task {i} running")
        await asyncio.sleep(1)

async def main():
    semaphore = asyncio.Semaphore(10)  # Максимум 10 одновременных
    tasks = [limited_task(semaphore, i) for i in range(1000)]
    await asyncio.gather(*tasks)

✅ Мониторь event loop

import asyncio

async def monitor_loop():
    loop = asyncio.get_event_loop()
    while True:
        # Вывод информации о нагрузке
        print(f"Event loop активен")
        await asyncio.sleep(1)

async def main():
    tasks = [
        monitor_loop(),
        heavy_operation(),
    ]
    await asyncio.gather(*tasks)

Заключение

Не стоит загружать event loop:

  1. Блокирующим синхронным кодом — используй асинхронные альтернативы
  2. CPU-bound задачами — перемещай в run_in_executor
  3. Неправильно приоритизированными задачами — используй семафоры
  4. Без мониторинга — отслеживай производительность

Event loop эффективен для I/O-bound задач (сеть, файлы, БД), но требует асинхронного кода и правильной архитектуры.

Почему не стоит в event loop много задач? | PrepBro