> Как работает асинхронный код в Python: конкурентный или параллельный, в одном или нескольких потоках? (Python)

Уровень: senior · Роль: backend · Язык: Python · Категория: Технические вопросы

Компании: ИП Калюков Николай Сергеевич

Стек: Python

> Пример ответа

Короткий ответ

Асинхронный код в Python - конкурентный, но не параллельный. Он выполняется в одном потоке на одном ядре CPU, переключаясь между задачами в точках ожидания (await). Параллелизм достигается только через multiprocessing или внешние библиотеки. Event loop управляет корутинами, обеспечивая конкурентное выполнение I/O-bound задач без блокировки потока.

Подробное объяснение

В Python асинхронность реализована через asyncio - event loop, который диспетчеризует корутины. Ключевое отличие от многопоточности: в любой момент времени выполняется ровно одна корутина, но переключение происходит кооперативно - только когда корутина сама отдаёт управление через await.

Это конкурентность, а не параллелизм: задачи логически выполняются одновременно (перекрываются по времени), но физически - последовательно. Параллелизм (одновременное выполнение на разных ядрах) в рамках одного процесса Python невозможен из-за GIL, и asyncio не пытается его обойти.

Модель выполнения:

  • Один поток, один event loop
  • Корутины - это генераторы с поддержкой await
  • Переключение контекста происходит только на I/O операциях или явных await
  • CPU-bound задачи блокируют event loop, поэтому для них asyncio не подходит

Сравнение с threading: потоки также конкурентны (из-за GIL), но переключение вытесняющее (по таймеру), а не кооперативное. asyncio даёт более предсказуемое поведение и меньше накладных расходов на переключение.

На практике

Для backend-разработки asyncio применяется для:

  • HTTP-серверов (aiohttp, FastAPI с async endpoints)
  • Работы с базами данных через async-драйверы (asyncpg, aiomysql)
  • Внешних API-вызовов (httpx, aiohttp)
  • WebSocket-соединений
  • Микросервисной коммуникации

Важно: асинхронный код не ускоряет CPU-bound задачи. Если есть тяжёлые вычисления - нужен multiprocessing или вынос в отдельный сервис. Смешивать asyncio и threading можно через run_in_executor, но это добавляет сложность.

На практике также важно следить за блокирующими вызовами внутри корутин - любой синхронный I/O (например, requests.get) заблокирует весь event loop.

Пример кода

PYTHON
import asyncio
import time
async def fetch_data(name: str, delay: float) -> str:
print(f"start {name}")
await asyncio.sleep(delay) # имитация I/O
print(f"done {name}")
return f"data from {name}"
async def main():
# конкурентное выполнение - все три корутины стартуют почти одновременно
results = await asyncio.gather(
fetch_data("first", 2),
fetch_data("second", 1),
fetch_data("third", 0.5)
)
print(results)
# замер времени
start = time.perf_counter()
asyncio.run(main())
print(f"total time: {time.perf_counter() - start:.2f}s")
# total time: ~2s, а не 3.5s - подтверждение конкурентности

Как отвечать на собеседовании

Начните с чёткого разделения конкурентности и параллелизма. Подчеркните, что asyncio - однопоточный, кооперативный планировщик. Упомяните GIL как причину отсутствия параллелизма в рамках одного процесса. Обязательно приведите пример, где asyncio даёт выигрыш (I/O-bound) и где не даёт (CPU-bound). Покажите понимание внутреннего устройства: event loop, корутины, задачи (Task), awaitable объекты.

Хорошо добавить сравнение с threading и multiprocessing, объяснив, когда что выбирать. Если спросят про производительность - упомяните накладные расходы на переключение контекста в потоках (~50-100 мкс) против ~1 мкс в asyncio.

Что проверяет интервьюер

Интервьюер оценивает:

  • Понимание фундаментальных концепций конкурентности и параллелизма
  • Знание внутреннего устройства asyncio (event loop, корутины, Task)
  • Умение объяснить, почему asyncio не подходит для CPU-bound задач
  • Понимание GIL и его влияния на threading и asyncio
  • Практический опыт: когда и как применять асинхронность в backend
  • Способность отличать кооперативное переключение от вытесняющего

Типичные ошибки

  • Утверждение, что asyncio выполняет задачи параллельно - это неверно, он только конкурентный
  • Использование asyncio для CPU-bound задач - event loop блокируется, выигрыша нет
  • Смешивание синхронных блокирующих вызовов (requests, time.sleep) внутри корутин - это убивает всю асинхронность
  • Забывают про GIL при обсуждении threading - потоки тоже не дают параллелизма для Python-кода
  • Путаница между asyncio.gather, asyncio.create_task и простым await - await последователен, gather конкурентен
  • Игнорирование того, что asyncio.run создаёт новый event loop каждый раз - нельзя вызывать его внутри работающего loop
  • Непонимание разницы между корутиной и Task: корутина - это функция, Task - обёртка для планирования

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью