Почему работает asyncio, в чем отличие от многопоточности, как работает event loop и переключение корутин в Python
Что проверяют
Отличаете ли вы конкурентность от параллелизма и понимаете ли, что переключение корутин кооперативное. Проверяют модель исполнения, а не знание синтаксиса. Следом идёт практический вопрос: что произойдёт, если внутри корутины вызвать блокирующий requests.get.
Как отвечать
Каркас: asyncio даёт конкурентность в одном потоке. Счётные задачи не ускоряются, выигрыш там, где программа ждёт сеть или диск.
- корутина: объект, умеющий приостанавливаться.
awaitне блокирует поток, а отдаёт управление вверх по стеку, до цикла событий; - задача: обёртка над корутиной, поставленная в очередь цикла через
asyncio.create_task. Без задачи илиawaitкорутина не исполнится; - цикл событий: бесконечный цикл поверх модуля
selectors(epoll на Linux, kqueue на macOS). На каждой итерации он выполняет готовые коллбэки из очередиcall_soon, спрашивает у селектора готовые сокеты с таймаутом до ближайшегоcall_laterи будит связанные задачи; - переключение: только в точках
awaitна реальной асинхронной операции, между ними код выполняется целиком.
Отличие от потоков: там переключение вытесняющее, система снимает поток в произвольном месте, поэтому блокировки нужны почти везде. Поток стоит стека в мегабайты, задача asyncio килобайты, и десятки тысяч задач норма. Взамен вы платите дисциплиной: одна блокирующая строка замораживает весь цикл.
Блокирующий код выносится через asyncio.to_thread или loop.run_in_executor. Для групп задач лучше asyncio.TaskGroup (3.11), он отменяет соседей при падении одной, в отличие от gather. Таймауты через asyncio.timeout, а в 3.14 живые задачи показывает python -m asyncio ps.
Где спотыкаются
Говорят, что корутины выполняются параллельно. Они идут по очереди в одном потоке, параллелизма без процессов тут нет.
Считают, что достаточно написать async def. Асинхронность даёт только неблокирующая библиотека внутри: asyncpg вместо синхронного psycopg2, httpx или aiohttp вместо requests.
Забывают, что исключение в задаче, которую никто не ожидал, тихо оседает в ней и всплывает только при сборке мусора. Отсюда правило: держать ссылки на задачи и собирать их через TaskGroup.