Этот вопрос проверяет, понимаете ли вы, почему потоки “перемешиваются” в выполнении и какие события заставляют CPython отдавать GIL другому потоку.
В CPython потоки переключаются, потому что один поток периодически освобождает GIL, и другой может его захватить. Это происходит по таймеру (интервал переключения) и в точках, где поток уходит ждать I/O. То есть операционная система планирует потоки, но выполнять Python-байткод реально может только тот, кто держит GIL. Поэтому вы видите “конкуренцию” потоков, но не настоящий параллелизм байткода.
Важно понимать общую картину: потоков может быть много, но байткод исполняет только один поток, который сейчас держит GIL. Переключение — это момент, когда GIL переходит к другому потоку.
Определение: Переключение потоков при GIL — ситуация, когда текущий поток освобождает GIL (добровольно или вынужденно), и другой поток захватывает его, продолжая выполнение байткода.
Перед началом перечисления подчеркну: это не одна причина, а несколько типичных триггеров.
Таймер переключения (switch interval)
CPython старается регулярно давать шанс другим потокам.
Интервал можно настраивать через sys.setswitchinterval(seconds).
Ожидание I/O
Когда поток вызывает операцию, которая долго ждет (например, чтение из сети), интерпретатор обычно отпускает GIL, чтобы другие потоки могли работать.
C-расширения могут отпускать GIL
Если тяжелая часть выполняется в C и разработчик расширения предусмотрел освобождение GIL, другие потоки могут исполнять байткод параллельно с этой C-работой (но это уже “частный случай”).
CPU-bound
Потоки будут часто “переключаться”, но суммарно работать почти как один поток (накладные расходы даже могут ухудшить время).
I/O-bound
Пока один поток ждет, другие реально делают полезную работу, поэтому потоки часто дают ускорение.
switch intervalimport sys
import threading
sys.setswitchinterval(0.001) # чаще переключаться
def ping(name):
for _ in range(5):
print(name)
t1 = threading.Thread(target=ping, args=("A",))
t2 = threading.Thread(target=ping, args=("B",))
t1.start(); t2.start()
t1.join(); t2.join()
# Вывод будет “перемешанным” из-за переключений
Планировщик ОС решает, какой поток “готов” выполняться, но GIL решает, кто реально выполняет Python-код.
Переключения не гарантируют “честность”: один поток может получать GIL чаще, чем другой, в зависимости от нагрузки и поведения кода.
Переключение потоков при GIL происходит, когда CPython регулярно или вынужденно освобождает GIL (таймер, ожидание I/O, C-код), поэтому потоки выглядят конкурентными, но байткод исполняется по очереди.