Есть ли ограничение на количество создаваемых тредов
Что проверяют
Знаете ли вы, что поток в CPython это настоящий поток операционной системы и откуда берётся предел. Следом спрашивают, сколько потоков вы бы запустили для I/O-задачи и когда лучше взять asyncio или процессы.
Как отвечать
Сам Python жёсткого лимита не ставит: threading.Thread создаёт системный поток, и упираетесь вы в ограничения ОС. На Linux это:
- лимиты ядра и пользователя:
ulimit -u(процессы и потоки пользователя),/proc/sys/kernel/threads-max,pid_max, а в контейнере ещёpids.maxв cgroup, у Docker он задаётся через--pids-limit; - память под стеки: каждому потоку резервируется стек, обычно 8 МБ виртуального адресного пространства. Физически заняты только тронутые страницы, но при тысячах потоков кончаются адресное пространство и
vm.max_map_count. Размер стека меняют черезthreading.stack_size().
При упоре start() бросит RuntimeError. Проблемы начинаются раньше: тысячи потоков тратят время на переключение контекста и борьбу за GIL.
Про GIL говорите точно: он не ограничивает число потоков, а не даёт двум потокам одновременно исполнять байткод. На вводе-выводе и в C-расширениях, отпускающих GIL, потоки работают параллельно. Поэтому потоки годятся для I/O, а для CPU-задач нужен multiprocessing или ProcessPoolExecutor. Есть сборка CPython без GIL (free-threaded, экспериментально с 3.13), но в продакшене она пока редкость.
Правильный ответ на «сколько потоков» это пул: ThreadPoolExecutor по умолчанию берёт min(32, число CPU + 4) воркеров. Для тысяч одновременных соединений лучше asyncio: корутина стоит килобайты, а не отдельный стек ОС.
Где спотыкаются
Говорят, что из-за GIL нельзя создать больше одного потока. Создать можно тысячи, ограничен только параллелизм байткода.
Называют правило «ядра умножить на два» для I/O. Для задач, которые ждут сеть, число определяется лимитами внешнего сервиса и замерами, а не ядрами.
Создают поток на каждый запрос и упираются в лимит под нагрузкой, вместо того чтобы ограничить конкурентность пулом или семафором.