В чем отличие многопоточности
Что проверяют
За коротким вопросом почти всегда стоит сравнение потоков, процессов и асинхронности, а для Python главное здесь GIL. Интервьюер хочет услышать, что вы выберете для задачи, упирающейся в сеть, и для задачи, нагружающей процессор. Следом обычно спрашивают, зачем нужны блокировки, если GIL и так один.
Как отвечать
Сначала общая разница: потоки живут внутри процесса и делят память, процессы изолированы и общаются через IPC. Потоки дешевле, зато общая память даёт гонки данных.
Затем специфика CPython:
- GIL: байткод Python в каждый момент исполняет только один поток процесса.
threadingне ускоряет вычисления на чистом Python, но подходит для I/O: на время ожидания сети или диска GIL отпускается. Его отпускают и многие C-расширения, например NumPy на тяжёлых операциях; threadingиThreadPoolExecutor: I/O с блокирующими библиотеками,requests, драйверы БД, файлы;multiprocessingиProcessPoolExecutor: CPU-задачи, у каждого процесса свой интерпретатор и свой GIL. Плата: запуск процессов и сериализация аргументов черезpickle;asyncio: один поток, одна событийная петля, тысячи соединений черезasync/await. Нужны асинхронные библиотеки (httpx,aiohttp,asyncpg), а один блокирующий вызов останавливает всю петлю.
Упомяните, что в Python 3.13 появилась экспериментальная сборка без GIL (free-threaded, PEP 703), в 3.14 она получила официальную поддержку, но стандартный интерпретатор по-прежнему идёт с GIL.
Где спотыкаются
Считают, что GIL избавляет от гонок. Он защищает внутренности интерпретатора, а counter += 1 из нескольких потоков не атомарна и может терять обновления, нужен threading.Lock.
Запускают CPU-вычисления в ThreadPoolExecutor и удивляются, что четыре потока работают не быстрее одного.
Вызывают time.sleep() или синхронный requests внутри корутины. Петля asyncio блокируется, остальные задачи ждут. Для таких вызовов есть asyncio.to_thread() и await asyncio.sleep().