Как работает синхронное программирование под капотом?
Что проверяют
Понимаете ли вы, что происходит с потоком, когда синхронный код ждёт сеть или диск, и чем это отличается от ожидания в asyncio. Следом обычно спрашивают про GIL: мешает ли он потокам, занятым вводом-выводом.
Как отвечать
Синхронный код выполняется строго по порядку: следующая строка начинается, когда закончилась предыдущая. Под капотом это выглядит так:
- интерпретатор: CPython компилирует код в байт-код и исполняет его в цикле вычисления. На каждый вызов функции создаётся кадр со своими локальными переменными, кадры образуют стек вызовов;
- блокирующий вызов:
sock.recv(),file.read()или запрос черезpsycopgв итоге становятся системным вызовом. Если данных ещё нет, ядро снимает поток с выполнения и будит, когда они придут. Процессор свободен для других потоков и процессов, но ваш поток стоит; - GIL: байт-код в каждый момент исполняет один поток. На время блокирующего ввода-вывода CPython отпускает GIL, поэтому для I/O потоки дают реальный выигрыш, а для вычислений нет. Между вычисляющими потоками интерпретатор переключается примерно раз в 5 мс (
sys.getswitchinterval()).
Отсюда способы масштабировать синхронный код: несколько процессов (воркеры gunicorn), потоки (ThreadPoolExecutor), процессы для вычислений (ProcessPoolExecutor). Асинхронный подход устроен иначе: сокеты неблокирующие, event loop через selectors (epoll на Linux, kqueue на macOS) ждёт готовности многих соединений в одном потоке, а корутина уступает управление на await.
Стоит упомянуть сборку CPython без GIL (free-threaded): экспериментально она появилась в 3.13, в 3.14 стала официально поддерживаемой, но по умолчанию GIL на месте.
Где спотыкаются
Отождествляют синхронный код с однопоточным. Синхронность это про порядок и блокировку, а потоков может быть сколько угодно.
Считают, что из-за GIL потоки бесполезны вообще. Для I/O они работают, GIL отпускается на время ожидания.
Вызывают time.sleep() или requests.get() внутри async def. Такой вызов блокирует весь event loop, и остальные корутины стоят вместе с ним.