В чем разница между мультипроцессной, мультипоточной и асинхронной работой
Что проверяют
Понимаете ли вы, какой из трёх подходов подходит под какую нагрузку и при чём тут GIL. Следом почти всегда дают задачу: вот парсер на тысячу URL, а вот расчёт признаков по большому массиву, что возьмёте для каждого.
Как отвечать
Сначала разведите понятия: конкурентность это умение вести много задач, перемежая их, параллельность это реальное одновременное выполнение на нескольких ядрах. Затем три модели:
- процессы (
multiprocessing,ProcessPoolExecutor): у каждого свой интерпретатор и своя память, GIL не мешает, ядра загружаются полностью. Цена: запуск процесса, сериализация аргументов и результатов черезpickle, отдельная память на воркер. Подходит для CPU-задач; - потоки (
threading,ThreadPoolExecutor): общая память, дешёвый старт. В обычном CPython GIL не даёт двум потокам одновременно выполнять байткод, но на время ввода-вывода отпускается. Поэтому потоки хороши для сети и диска, особенно с синхронными библиотеками вродеrequests, и не ускоряют чистые вычисления; - асинхронность (
asyncio): один поток и цикл событий, задачи сами уступают управление наawait. Тысячи соединений обходятся дёшево, но всё должно быть неблокирующим:httpx,aiohttp,asyncpg.
Стоит упомянуть, что с Python 3.13 существует отдельная сборка без GIL (free-threaded), но по умолчанию GIL на месте. А расширения на C вроде NumPy часто отпускают GIL сами, поэтому потоки над ними иногда параллелятся.
Где спотыкаются
Вызывают блокирующий код внутри корутины: time.sleep или requests.get в async def останавливают весь цикл событий. Такие вызовы выносят через asyncio.to_thread.
Говорят, что потоки в Python бесполезны. Для ввода-вывода они работают нормально, проигрывают они только на чистых вычислениях.
Забывают про стоимость процессов: гоняют через пул мелкие задачи с большими аргументами, и сериализация съедает выигрыш. На macOS и Windows процессы стартуют через spawn, и код без if __name__ == "__main__": ломается.