Асинхронность и asyncio
Что делает await и чего он не делает?
await приостанавливает текущую корутину до завершения awaitable-объекта и позволяет циклу событий выполнять другую готовую работу. Состояние корутины сохраняется. Оператор сам не создает конкурентность и не переносит код в поток: последовательные await остаются последовательными. Для независимых операций создают управляемые задачи. Если вызываемая работа синхронно блокирует поток, await внутри нее не появится и event loop остановится. Проверяют отмену и остановку сервиса, потому что успешное ожидание не доказывает корректный жизненный цикл ресурса.
Ссылки для изучения
Примеры хороших ответов из реальных собеседований
- Мок-интервью Python backend-разработчика · 30:15–30:55Мок-собеседование · Ответ кандидата
Кандидат объясняет await как точку, в которой корутина передаёт управление циклу событий.
Как event loop планирует корутины?
Event loop держит готовые callbacks и задачи, следит за I/O и таймерами. Корутина выполняется до точки, где await действительно должен ждать, затем управление получает цикл; после готовности операции задача снова попадает в очередь. Обычно все это происходит в одном потоке, поэтому долгий синхронный вызов задерживает всех. Планировщик обеспечивает конкурентное продвижение, но не обещает строгую справедливость или параллельность Python-кода. Проверяют отмену и остановку сервиса, потому что успешное ожидание не доказывает корректный жизненный цикл ресурса.
Ссылки для изучения
Примеры хороших ответов из реальных собеседований
- Мок-интервью Python backend-разработчика · 28:35–29:27Мок-собеседование · Ответ кандидата
Кандидат объясняет, что на await корутина отдаёт управление event loop, тот запускает другую готовую корутину и позднее возобновляет ожидавшую задачу.
Что такое корутина и как она запускается?
Функция 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 свойства параллелизма и расширений нужно проверять отдельно. Проверяют отмену и остановку сервиса, потому что успешное ожидание не доказывает корректный жизненный цикл ресурса.
Ссылки для изучения
Примеры хороших ответов из реальных собеседований
- Python: техническое собеседование Junior-разработчика · 34:09–34:30Мок-собеседование · Ответ кандидата
Кандидат объясняет, что длинная CPU-bound операция занимает поток event loop и не даёт другим корутинам выполняться.
Чем блокирующий I/O отличается от неблокирующего?
Блокирующая операция удерживает поток до результата. Неблокирующий API регистрирует ожидание и позволяет потоку заняться другой работой, а готовность сообщает ОС или runtime. В asyncio синхронный файловый или сетевой клиент внутри async def все равно блокирует event loop. Поэтому важна не метка функции, а вся цепочка библиотек; несовместимый вызов заменяют async-клиентом или изолируют в ограниченном executor. Проверяют отмену и остановку сервиса, потому что успешное ожидание не доказывает корректный жизненный цикл ресурса.
Ссылки для изучения
Примеры хороших ответов из реальных собеседований
- Мок-собеседование на Middle Python-разработчика: архитектура и асинхронность · 1:46:40–1:48:00Мок-собеседование · Ответ кандидата
Кандидат объясняет, что блокирующий I/O ждёт фактического ввода и останавливает дальнейшее выполнение, в отличие от неблокирующего режима.
Чем 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 внутри процесса не делает атомарной запись во внешнюю БД; там нужны транзакция, ограничение уникальности или условное обновление. Проверяют отмену и остановку сервиса, потому что успешное ожидание не доказывает корректный жизненный цикл ресурса.
Ссылки для изучения
Примеры хороших ответов из реальных собеседований
- Middle Python-мок-интервью о конкурентности и PostgreSQL · 46:30–47:48Мок-собеседование · Совместный разбор
Кандидат подтверждает, что race condition возможен в event loop, называет asyncio.Semaphore/Event и разбирает пример общего состояния с переключением в await.
Как контролировать жизненный цикл фоновой 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. Проверяют отмену и остановку сервиса, потому что успешное ожидание не доказывает корректный жизненный цикл ресурса.
Ссылки для изучения
Примеры хороших ответов из реальных собеседований
- Как проходит собеседование на Middle Python разработчика: Вопросы и разбор кейса · 11:51–12:44Мок-собеседование · Ответ кандидата
Кандидат объясняет, что CPU-intensive функция может занять event loop и не дать ему переключиться на внешние события.







