Что происходит с остальными потоками, если один из четырёх потоков выполняет бесконечный цикл?
Что проверяют
Понимаете ли вы, как именно GIL распределяет время между потоками. Ответ «всё встанет» и ответ «ничего не изменится» одинаково неверны, интервьюер ждёт середины.
Как отвечать
Главное: чистый цикл на Python не удерживает GIL навсегда. Интерпретатор просит текущий поток отдать блокировку по истечении интервала переключения, это несколько миллисекунд, значение читается через sys.getswitchinterval. Поэтому остальные три потока продолжают работать, но делят процессорное время с обжорой и заметно тормозят.
Дальше уточните, когда картина другая:
- цикл ушёл в расширение на C, которое не отпускает блокировку: тогда остальные потоки действительно встают до его выхода;
- цикл держит
threading.Lock, нужный соседям: встанут те, кто ждёт этот замок; - поток занимает ядро целиком, и в веб-приложении это видно как рост времени ответа у всех запросов, а не у одного.
Про остановку: штатного способа убить поток снаружи нет. Флаг daemon=True лишь разрешает процессу завершиться, не дожидаясь потока, обычный поток удержит процесс живым. Выход закладывают заранее: проверка threading.Event внутри цикла, а счётная нагрузка уезжает в отдельные процессы через ProcessPoolExecutor.
Где спотыкаются
Говорят «поток захватил GIL, приложение зависло». Для байт-кода это неверно, переключение принудительное, и симптом другой: равномерная деградация вместо полной остановки. Диагностика от этого меняется, смотрят загрузку ядер и стеки потоков, а не ищут взаимоблокировку.
Повторяют правило про переключение каждые сто инструкций байт-кода. Так работал старый интерпретатор, современный отмеряет время, а не количество инструкций, и на счётном цикле ведёт себя иначе.
Обещают прибить поток снаружи приватными функциями. Асинхронная подсадка исключения не сработает, пока поток сидит в сишном вызове, а если сработает, захваченные замки останутся захваченными, и вместо зависшего потока получится зависшее приложение.