В чем разница между многопоточностью и многопроцессорностью и когда что лучше использовать
Что проверяют
Для специалиста по данным это вопрос про Python и GIL: понимаете ли вы, почему потоки не ускоряют расчёт на чистом Python и когда они всё же полезны. Следом часто разбирают конкретный случай: скачать тысячи файлов из S3 или посчитать признаки на pandas.
Как отвечать
- поток: живёт внутри процесса и делит с другими потоками память. Создаётся дёшево, обмен данными бесплатный, но нужна синхронизация;
- процесс: своё адресное пространство и свой интерпретатор. Запуск дороже, данные передаются через сериализацию
pickle, зато падение одного процесса не роняет остальные; - GIL: в стандартном CPython байткод в каждый момент исполняет только один поток. Для CPU-bound кода на чистом Python потоки бесполезны, а для I/O-bound (сеть, диск, запросы к базе) работают отлично: на время ожидания GIL отпускается;
- исключения:
NumPy, векторные операцииpandas,scikit-learnи PyTorch отпускают GIL внутри C-кода, и потоки там дают реальный параллелизм. Сборка CPython без GIL (free-threaded) уже существует, но поддерживают её пока не все библиотеки.
Практический выбор: скачивание и запросы к API через ThreadPoolExecutor или asyncio, тяжёлый расчёт на Python через ProcessPoolExecutor, multiprocessing.Pool или joblib. Загрузка данных в PyTorch с num_workers > 0 в DataLoader как раз идёт в отдельных процессах.
Где спотыкаются
Утверждают, что многопоточность в Python «не работает вообще». Для I/O она работает, а внутри NumPy расчёт тоже может идти параллельно.
Не учитывают стоимость передачи данных. Отправить в пул процессов датафрейм на несколько гигабайт значит сериализовать его для каждого воркера, и накладные расходы съедают выигрыш. Помогают multiprocessing.shared_memory, memmap в joblib или деление данных по файлам.
Забывают про способ запуска процессов. Если родитель уже инициализировал CUDA, в дочернем процессе после fork она не заработает, нужен spawn. А на macOS и Windows spawn стоит по умолчанию, и скрипт без if __name__ == "__main__": там падает при старте пула.