← Назад к вопросам
В каком случае Event Loop в asyncio может быть заблокирован
3.0 Senior🔥 121 комментариев
#Python Core#Асинхронность и многопоточность
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
# Event Loop блокировка в asyncio
Event Loop в asyncio может быть заблокирован, если в асинхронный код добавить синхронные операции. Это одна из наиболее частых ошибок при работе с asyncio. Разберём все случаи и как их избежать.
Как работает Event Loop
Event Loop — это цикл, который проверяет готовность операций и переключается между ними:
# Упрощённая модель Event Loop
while not stopped:
# 1. Проверить, какие операции готовы (I/O, таймеры)
ready = check_ready_operations()
# 2. Выполнить одну операцию
for task in ready:
task.run() # Это должно быть быстро!
# 3. Если нет готовых операций, подождать
sleep_until_next_ready()
Блокировка происходит, когда task.run() занимает слишком много времени.
1. Синхронные I/O операции
Самая частая ошибка — использовать синхронные функции в асинхронном коде:
Проблема: requests вместо httpx
import asyncio
import requests # СИНХРОННЫЙ
import httpx # АСИНХРОННЫЙ
async def fetch_data_wrong():
# БЛОКИРУЕТ Event Loop на 1 сек
response = requests.get("https://api.example.com/data")
return response.json()
async def fetch_data_correct():
# Не блокирует Event Loop
async with httpx.AsyncClient() as client:
response = await client.get("https://api.example.com/data")
return response.json()
async def main():
# Если использовать неправильный код:
# Event Loop ждёт 1 сек, другие задачи не выполняются
task1 = asyncio.create_task(fetch_data_wrong())
task2 = asyncio.create_task(other_async_task())
# task2 не стартует, пока task1 не вернёт управление!
await asyncio.gather(task1, task2)
Проблема: синхронные database операции
from sqlalchemy import create_engine # СИНХРОННЫЙ
from sqlalchemy.ext.asyncio import create_async_engine # АСИНХРОННЫЙ
# Неправильно
engine = create_engine("postgresql://user:pass@localhost/db")
async def get_users_wrong():
session = Session(engine)
users = session.query(User).all() # БЛОКИРУЕТ на 100ms
return users
# Правильно
async_engine = create_async_engine("postgresql+asyncpg://user:pass@localhost/db")
async def get_users_correct():
async with AsyncSession(async_engine) as session:
result = await session.execute(select(User))
return result.scalars().all()
2. CPU-интенсивные операции
Даже без I/O, долгие вычисления блокируют Event Loop:
import asyncio
import time
# Проблема: длительные вычисления
async def fibonacci_wrong(n):
"""Блокирует Event Loop на 10+ сек"""
if n < 2:
return n
return fibonacci_wrong(n-1) + fibonacci_wrong(n-2)
async def main():
task1 = asyncio.create_task(fibonacci_wrong(40)) # ~40 сек на CPU
task2 = asyncio.create_task(asyncio.sleep(1)) # Должно заснуть на 1 сек
await asyncio.gather(task1, task2)
# task2 не начнётся, пока task1 не закончится
Решение — переместить в отдельный процесс:
from concurrent.futures import ProcessPoolExecutor
def fibonacci(n): # Синхронная функция
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)
async def fibonacci_async(n):
"""Не блокирует Event Loop"""
loop = asyncio.get_event_loop()
with ProcessPoolExecutor() as pool:
result = await loop.run_in_executor(pool, fibonacci, n)
return result
async def main():
task1 = asyncio.create_task(fibonacci_async(40)) # На отдельном процессе
task2 = asyncio.create_task(asyncio.sleep(1)) # Начнётся сразу
await asyncio.gather(task1, task2)
# task2 завершится в 1 сек, task1 будет выполняться параллельно
3. Синхронные файловые операции
import asyncio
import aiofiles
import time
# Неправильно: открытие больших файлов
async def read_file_wrong(path):
with open(path, 'r') as f: # БЛОКИРУЕТ на 100ms
return f.read()
# Правильно
async def read_file_correct(path):
async with aiofiles.open(path, 'r') as f:
return await f.read()
async def demonstrate_blocking():
# Если read_file_wrong:
task1 = asyncio.create_task(read_file_wrong('large_file.txt')) # 100ms
start = time.time()
task2 = asyncio.create_task(asyncio.sleep(0.05)) # 50ms
await asyncio.gather(task1, task2)
elapsed = time.time() - start
print(f"Elapsed: {elapsed:.2f}s") # 0.10s (task2 ждал task1)
4. Блокирующие библиотеки
import asyncio
import logging
import json
# Проблема: блокирующие библиотеки
async def process_data_wrong():
data = {"users": list(range(1000000))}
# json.dumps может блокировать на большых объёмах
json_str = json.dumps(data) # МОЖЕТ БЛОКИРОВАТЬ
logging.info(json_str) # БЛОКИРУЕТ файловый I/O
# PIL обработка изображений
from PIL import Image
img = Image.open('large_image.jpg') # БЛОКИРУЕТ
img = img.resize((1920, 1080)) # БЛОКИРУЕТ
return json_str
# Решение: run_in_executor
async def process_data_correct():
loop = asyncio.get_event_loop()
data = {"users": list(range(1000000))}
# Запустить в thread pool
json_str = await loop.run_in_executor(
None, # используем default ThreadPoolExecutor
json.dumps,
data
)
# Логирование (если критично для производительности)
# await async_logger.info(json_str)
return json_str
5. Блокирующие сетевые операции (socket)
import asyncio
import socket
# Проблема: использование socket напрямую
async def connect_wrong(host, port):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((host, port)) # БЛОКИРУЕТ
sock.send(b"Hello") # БЛОКИРУЕТ
data = sock.recv(1024) # БЛОКИРУЕТ
return data
# Правильно: использовать asyncio.open_connection
async def connect_correct(host, port):
reader, writer = await asyncio.open_connection(host, port)
writer.write(b"Hello")
await writer.drain() # Ждём, пока буфер опустеет
data = await reader.read(1024)
writer.close()
return data
6. Непрерывные вычисления (Busy Loop)
import asyncio
# Проблема: цикл без await
async def busy_loop_wrong():
# Это БЛОКИРУЕТ Event Loop!
for i in range(1_000_000_000):
pass # Нет await, Event Loop не может переключиться
return "done"
# Решение: добавить периодические await
async def busy_loop_correct():
for i in range(1_000_000_000):
if i % 10000 == 0: # Каждые 10k итераций
await asyncio.sleep(0) # Отдаёт контроль Event Loop'у
return "done"
async def demonstrate():
# Неправильный вариант
task1 = asyncio.create_task(busy_loop_wrong())
task2 = asyncio.create_task(asyncio.sleep(0.1))
done, pending = await asyncio.wait(
[task1, task2],
timeout=1
)
# task2 не завершится в отведённое время!
print(f"Done: {len(done)}, Pending: {len(pending)}")
7. Блокирующие операции в обработчиках
from fastapi import FastAPI, BackgroundTasks
import time
app = FastAPI()
# Проблема: медленная операция в эндпоинте
@app.get("/wrong")
async def endpoint_wrong():
time.sleep(1) # БЛОКИРУЕТ Event Loop на 1 сек
return {"status": "ok"}
# Решение: асинхронная функция
@app.get("/correct")
async def endpoint_correct():
await asyncio.sleep(1) # Отдаёт контроль другим задачам
return {"status": "ok"}
# Или использовать background task
@app.get("/background")
async def endpoint_with_bg(background_tasks: BackgroundTasks):
background_tasks.add_task(slow_operation) # Выполнится в фоне
return {"status": "processing"}
def slow_operation():
time.sleep(1)
Как обнаружить блокировку Event Loop
import asyncio
import logging
# Логирование медленных операций
logging.basicConfig(level=logging.DEBUG)
loop = asyncio.get_event_loop()
# Метод 1: Slow callback detection
loop.slow_callback_duration = 0.1 # Логирует callback'и > 100ms
# Метод 2: Вручную мониторить время
async def check_blocking():
import time
start = time.perf_counter()
await asyncio.sleep(0) # Отдаём контроль
elapsed = time.perf_counter() - start
if elapsed > 0.05: # > 50ms значит Event Loop был занят
print(f"Event Loop заблокирован на {elapsed*1000:.1f}ms")
# Метод 3: Использовать asyncio.Task
task = asyncio.create_task(check_blocking())
time.sleep(1) # Это БЛОКИРУЕТ
await task # task будет показывать блокировку
Таблица асинхронных альтернатив
┌──────────────────────┬─────────────────────┬──────────────────┐
│ Синхронное │ Асинхронное │ Альтернатива │
├──────────────────────┼─────────────────────┼──────────────────┤
│ requests.get() │ httpx.get() │ await httpx... │
│ open() │ aiofiles.open() │ await aiofiles...│
│ socket.connect() │ asyncio.open_con() │ await asyncio... │
│ time.sleep(n) │ asyncio.sleep(n) │ await asyncio... │
│ db.query() │ async db.query() │ await async... │
│ json.dumps() │ loop.run_executor() │ await loop... │
│ fibonacci(n) │ loop.run_executor() │ await loop... │
│ logging.info() │ async_logger │ await async... │
└──────────────────────┴─────────────────────┴──────────────────┘
Ключевые выводы
- Никогда не используйте time.sleep() в async коде — используйте await asyncio.sleep()
- Используйте асинхронные библиотеки — httpx, aiofiles, asyncpg
- Для CPU-интенсивных операций — используйте run_in_executor()
- Для файловых операций — используйте aiofiles
- Добавляйте await asyncio.sleep(0) в длительные циклы
- Мониторьте блокировки — используйте slow_callback_duration
Помните: Event Loop — это одна threads, которая должна выполняться очень быстро!