Вопрос с собеседования · Python

Что будет, если внутри async-функции вызвать блокирующий код?

MiddleКонкурентностьasyncio
Ещё спрашивают так:
  • Как вызвать синхронную библиотеку из asyncio?
  • Зачем нужны asyncio.to_thread и run_in_executor?
Коротко

Встанет весь event loop: блокирующий вызов (time.sleep, requests.get, синхронный драйвер БД, тяжёлое вычисление) не отдаёт управление, и все остальные задачи ждут. Решение — асинхронная библиотека (httpx, asyncpg, aiofiles) или вынос в поток: asyncio.to_thread / loop.run_in_executor.

  • Для CPU-тяжёлого — ProcessPoolExecutor: потоки упрутся в GIL
  • Найти блокировки: asyncio debug mode предупреждает о шагах дольше 100 мс
  • Симптом — растут задержки всех запросов, хотя CPU почти не загружен

А ещё Во FastAPI обычный def-обработчик сам выполняется в пуле потоков, async def — в цикле

Подробно

Почему так. Цикл переключает задачи только в точках await. Синхронная функция не содержит await — пока она работает, цикл стоит. Сервер на asyncio с одним requests.get на 2 секунды в обработчике на эти 2 секунды перестаёт отвечать всем.

Варианты решения:

# 1. асинхронная библиотека — лучший вариант
async with httpx.AsyncClient() as client:
    r = await client.get(url)

# 2. блокирующий ввод-вывод — в поток
data = await asyncio.to_thread(legacy_client.fetch, url)

# 3. вычисления — в процесс
loop = asyncio.get_running_loop()
with ProcessPoolExecutor() as pool:
    result = await loop.run_in_executor(pool, heavy_compute, data)

to_thread использует пул потоков цикла по умолчанию (ограниченного размера) — массовые блокирующие вызовы в нём тоже выстроятся в очередь.

Как найти: asyncio.run(main(), debug=True) или PYTHONASYNCIODEBUG=1 логирует «Executing … took 0.5 seconds». В проде — метрика задержки цикла (lag) и профилировщик (py-spy).

Тонкость FastAPI: обработчик, объявленный через def, FastAPI сам запускает в пуле потоков, а async def — прямо в цикле. Блокирующий вызов внутри async def — частая ошибка.

Что хотят услышать

  • Блокировка всего цикла и почему.
  • to_thread, run_in_executor, асинхронные библиотеки.
  • Процессы для вычислений.
  • Как обнаружить.

Частые ошибки

  • time.sleep вместо asyncio.sleep.
  • Синхронный драйвер БД в async-обработчике.

Проверьте себя

Один верный вариант. Подумайте, потом откройте ответ.

Показать варианты и ответ
  • ✗Блокируется только текущая корутина, остальные продолжают работать в том же цикле
  • ✗asyncio сам замечает блокирующие вызовы и автоматически выносит их в пул потоков
  • ✗Для CPU-тяжёлых вычислений лучше всего asyncio.to_thread: поток в нём обходит GIL
  • ✓Встанет весь event loop; нужна async-библиотека или asyncio.to_thread

Что спросят дальше

Разбор темы

Связанные вопросы

Партнёр платформы

Разбор собеседования с sotto

После собеседования sotto разбирает разговор: оценка, каждый вопрос — хорошо, частично или слабо, чего не хватило в ответе и над чем поработать к следующему разу. Первые 30 минут бесплатно.

Разбор собеседования