Перейти к содержимому
На этой странице

Асинхронность и asyncio

Все темы Python Developer

Что делает await и чего он не делает?

await приостанавливает текущую корутину до завершения awaitable-объекта и позволяет циклу событий выполнять другую готовую работу. Состояние корутины сохраняется. Оператор сам не создает конкурентность и не переносит код в поток: последовательные await остаются последовательными. Для независимых операций создают управляемые задачи. Если вызываемая работа синхронно блокирует поток, await внутри нее не появится и event loop остановится. Проверяют отмену и остановку сервиса, потому что успешное ожидание не доказывает корректный жизненный цикл ресурса.


Как event loop планирует корутины?

Event loop держит готовые callbacks и задачи, следит за I/O и таймерами. Корутина выполняется до точки, где await действительно должен ждать, затем управление получает цикл; после готовности операции задача снова попадает в очередь. Обычно все это происходит в одном потоке, поэтому долгий синхронный вызов задерживает всех. Планировщик обеспечивает конкурентное продвижение, но не обещает строгую справедливость или параллельность Python-кода. Проверяют отмену и остановку сервиса, потому что успешное ожидание не доказывает корректный жизненный цикл ресурса.


Что такое корутина и как она запускается?

Функция async def при вызове возвращает объект-корутину, но ее тело еще не обязано выполняться. Корутину запускают через await, asyncio.create_task() внутри работающего цикла или верхнеуровневый asyncio.run(). Модель эффективна для множества операций ожидания, если используемые библиотеки неблокирующие. Вычисления без точек ожидания занимают поток event loop и требуют другого способа исполнения. Проверяют отмену и остановку сервиса, потому что успешное ожидание не доказывает корректный жизненный цикл ресурса.


Зачем нужна asyncio.Task?

asyncio.create_task(coro) оборачивает корутину в Task и планирует ее конкурентное выполнение. Task хранит состояние, результат или исключение, ее можно дождаться и запросить отмену. Ссылку на задачу сохраняют, а результат и ошибку обязательно собирают. Связанные задачи удобнее запускать в TaskGroup, который ждет всех и управляет сбоями. Массовый create_task ограничивают семафором, очередью или пулом ресурсов. Проверяют отмену и остановку сервиса, потому что успешное ожидание не доказывает корректный жизненный цикл ресурса.


Почему CPU-bound код блокирует event loop?

CPU-bound функция без await не возвращает управление event loop, поэтому таймеры, сокеты и остальные задачи ждут. Простое помещение вычисления в async def этого не меняет. Блокирующий вызов можно вынести через asyncio.to_thread, если библиотека освобождает GIL или работа в основном I/O. Для тяжелого чистого Python обычно выбирают процессы. В free-threaded сборках CPython свойства параллелизма и расширений нужно проверять отдельно. Проверяют отмену и остановку сервиса, потому что успешное ожидание не доказывает корректный жизненный цикл ресурса.


Чем блокирующий I/O отличается от неблокирующего?

Блокирующая операция удерживает поток до результата. Неблокирующий API регистрирует ожидание и позволяет потоку заняться другой работой, а готовность сообщает ОС или runtime. В asyncio синхронный файловый или сетевой клиент внутри async def все равно блокирует event loop. Поэтому важна не метка функции, а вся цепочка библиотек; несовместимый вызов заменяют async-клиентом или изолируют в ограниченном executor. Проверяют отмену и остановку сервиса, потому что успешное ожидание не доказывает корректный жизненный цикл ресурса.


Чем asyncio.Future отличается от Task?

Future представляет будущий результат и бывает pending, завершенным или отмененным; кто-то другой устанавливает ему значение или исключение. Task является специализированным Future, который сам продвигает корутину при планировании event loop. Оба объекта можно await и отменять, но прикладной код редко создает Future вручную. Он чаще получает Task или Future от библиотеки и отвечает за сбор результата и исключения. Проверяют отмену и остановку сервиса, потому что успешное ожидание не доказывает корректный жизненный цикл ресурса.


Как реализовать асинхронный контекстный менеджер?

Асинхронный менеджер реализует async __aenter__ и async __aexit__ либо создается через @asynccontextmanager. Получение ресурса идет до yield, значение передается в async with, а закрытие помещают в finally и можно выполнить через await. Исключение из тела приходит менеджеру; его обычно не подавляют. Операции входа и выхода тоже должны быть неблокирующими, иначе они остановят event loop. Проверяют отмену и остановку сервиса, потому что успешное ожидание не доказывает корректный жизненный цикл ресурса.


Может ли возникнуть race condition в asyncio?

Да. Корутины выполняются в одном потоке, но переключение на await между чтением и записью общего состояния позволяет другой задаче изменить его. Критическую секцию защищают asyncio.Lock, а передачу данных часто проще строить через Queue и одного владельца состояния. Lock внутри процесса не делает атомарной запись во внешнюю БД; там нужны транзакция, ограничение уникальности или условное обновление. Проверяют отмену и остановку сервиса, потому что успешное ожидание не доказывает корректный жизненный цикл ресурса.


Как контролировать жизненный цикл фоновой asyncio.Task?

У фоновой Task должен быть владелец и срок жизни. Сохраняют сильную ссылку, собирают результат и исключение, а при остановке вызывают cancel() и затем await, давая finally закрыть ресурс. Таймаут ограничивает ожидание, но тоже реализуется отменой. Связанные задачи лучше держать в TaskGroup; бесконтрольный fire-and-forget теряет ошибки и может пережить запрос, которому задача принадлежала. Проверяют отмену и остановку сервиса, потому что успешное ожидание не доказывает корректный жизненный цикл ресурса.


Где возникает backpressure в async-сервисе?

Backpressure появляется там, где вход быстрее ограниченного ресурса: очередь запросов, пул БД, лимит внешнего API, сетевой буфер или consumer lag. Вместо создания неограниченного числа Task ставят bounded queue, semaphore и timeout, а при переполнении замедляют чтение или отклоняют работу. Наблюдают длину и возраст очереди, время ожидания ресурса, число отказов и utilization, иначе задержка скрыто растет до OOM. Проверяют отмену и остановку сервиса, потому что успешное ожидание не доказывает корректный жизненный цикл ресурса.


Когда синхронный код быстрее async?

Синхронный код часто быстрее для короткой CPU-операции или последовательного сценария без конкурентного ожидания: у event loop, coroutine и Task есть накладные расходы. Async выигрывает throughput, когда много операций независимо ждут сеть или другой неблокирующий ресурс. Он не ускоряет одно вычисление и может ухудшить latency при блокирующей зависимости. Выбор подтверждают профилем нагрузки, а не самим наличием async API. Проверяют отмену и остановку сервиса, потому что успешное ожидание не доказывает корректный жизненный цикл ресурса.

Собеседования: Python

Смотри записи интервью, узнай, какие вопросы задают и как отвечают кандидаты.

Вопросы и ответы

Не нашли ответ? Напишите мне в чат. Я делаю Шпаргалку и сам отвечаю на сообщения. Расскажите, что не работает или чего вам не хватает. Может, смогу сразу взять это в работу.

Откуда взяты вопросы?

Из реальных собеседований. Основой подборки стал опыт Вадима Новосёлова: он проходил интервью и записывал вопросы. Подробнее о материалах.

Насколько эти вопросы актуальны?

Эти вопросы встречались нам на реальных собеседованиях в 2025 году. Мы регулярно проходим собеседования и пополняем подборку новыми вопросами. Основы профессии и ключевые технологии остаются востребованными годами, а детали конкретных инструментов и версий стоит сверять с текущей документацией.

На какой уровень рассчитана подборка?

Мы проходили собеседования на вакансии уровня Middle+, а иногда и на Senior-позиции. Вопросы из этих интервью вошли в подборку. Направления работы: Python-разработчик, Backend-разработчик на Python. Глубина обсуждения зависит от вакансии: будь готов объяснить основную идею, привести практический пример и разобрать ограничения и альтернативы решения.

Этот вопрос точно будет на моём собеседовании?

Гарантии нет: набор вопросов зависит от компании, задач команды, уровня вакансии и самого интервьюера. Эти вопросы уже встречались на реальных собеседованиях, но на твоём интервью ту же тему могут проверить другой формулировкой, практической задачей или обсуждением твоего опыта. Используй подборку, чтобы разобраться в теме: объясняй идею своими словами, приводи примеры и готовься обсудить ограничения и альтернативы решения. Так будет проще ответить и на знакомый вопрос, и на неожиданные уточнения.