В чем разница между мультитредингом и мультипроцессингом в Python
Что проверяют
Понимаете ли вы, как GIL влияет на реальный код с NumPy, pandas и PyTorch, а не только помните фразу «потоки для I/O, процессы для CPU». Следом обычно спрашивают, почему DataLoader работает на процессах и чего стоит передача данных между ними.
Как отвечать
- потоки (
threading,ThreadPoolExecutor): делят память одного процесса. В обычном CPython байткод Python в каждый момент выполняет один поток, который держит GIL. Но GIL отпускается на ожидании сети и диска и во многих тяжёлых операциях в C-коде: матричные операции NumPy, часть кода scikit-learn и PyTorch. Поэтому скачивание файлов или параллельные запросы к API на потоках ускоряются хорошо; - процессы (
multiprocessing,ProcessPoolExecutor,joblib): у каждого свой интерпретатор и свой GIL, чистый Python на CPU реально идёт на нескольких ядрах. Цена: запуск процесса и сериализация черезpickleвсего, что передаётся туда и обратно. Большой датафрейм, отправляемый в каждую задачу, съедает выигрыш; - метод запуска:
fork,spawnилиforkserver. На macOS и Windows по умолчаниюspawn, в Linux с Python 3.14forkserver, раньше былfork; - сборка без GIL: по PEP 703 есть free-threaded CPython, в 3.13 экспериментальный, в 3.14 официально поддерживаемый. Это отдельная сборка, и не все C-расширения с ней совместимы.
Практическое правило для ML: сеть и внешние сервисы на потоках или asyncio, предобработка на чистом Python в процессах, а векторизованный NumPy часто не требует ни того ни другого.
Где спотыкаются
Говорят, что потоки в Python бесполезны из-за GIL. Для I/O и кода, отпускающего GIL, это неверно.
Забывают про переподписку: 8 процессов, в каждом BLAS запускает по 8 потоков, и 64 потока делят 8 ядер. Лечится OMP_NUM_THREADS или threadpoolctl.
Не пишут if __name__ == "__main__": и падают на spawn, или делают fork после инициализации CUDA и получают Cannot re-initialize CUDA in forked subprocess.