Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Почему не стоит загружать 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:
- Блокирующим синхронным кодом — используй асинхронные альтернативы
- CPU-bound задачами — перемещай в
run_in_executor - Неправильно приоритизированными задачами — используй семафоры
- Без мониторинга — отслеживай производительность
Event loop эффективен для I/O-bound задач (сеть, файлы, БД), но требует асинхронного кода и правильной архитектуры.