> Как работает асинхронный код в 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.
Пример кода
PYTHONimport asyncioimport timeasync def fetch_data(name: str, delay: float) -> str:print(f"start {name}")await asyncio.sleep(delay) # имитация I/Oprint(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 - обёртка для планирования
> Похожие задачи по Python
Что означает двойное подчеркивание в начале имени метода или атрибута класса в Python?
Какие механизмы инкапсуляции реализованы в Python?
Что выбрать для кэша рядом с базой данных: Redis, RabbitMQ или Kafka?
Сколько ядер нужно для обработки миллиона пользователей за 4 часа, если расчет одного пользователя занимает 1 секунду?
> Похожие задачи по backend
Что означает двойное подчеркивание в начале имени метода или атрибута класса в Python?
Какие механизмы инкапсуляции реализованы в Python?
Что выбрать для кэша рядом с базой данных: Redis, RabbitMQ или Kafka?
Сколько ядер нужно для обработки миллиона пользователей за 4 часа, если расчет одного пользователя занимает 1 секунду?
> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?
Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью