Канон / лаборатория 3

GIL во времени

Кто держит блокировку, кто её ждёт и кто работает без неё — по трём типам нагрузки, рядом с настоящими замерами.

CPython 3.11.15Linux x86-64, 2 ядрак главам 19 и 20

О GIL написаны сотни статей, и большинство из них повторяет одну ошибку: считает, что GIL делает потоки бесполезными. Он делает бесполезным ровно один сценарий — чистый Python, упирающийся в процессор. Ниже видно, чем эти три случая отличаются: диаграмма — модель поведения, числа рядом с ней — настоящие замеры на этой машине.

нагрузка
потоков
интервал переключения
0 мс
держит GIL ждёт GIL считает в C, GIL отпущен спит на I/O

1Второй эффект: интервал переключения — это задержка

Первая диаграмма показывает пропускную способность. Есть эффект, который она не показывает, и на проде он болезненнее: поток, ждущий ответа сети, не получит управление раньше, чем истечёт интервал переключения занятого потока. Один CPU-bound поток в процессе — и латентность всего веб-сервиса привязывается к этому числу.

Замер: один поток крутит пустой цикл, второй делает time.sleep(0.001) и меряет, насколько поздно проснулся. Здесь всё настоящее, без модели.

sys.setswitchinterval

2Проверить руками

import sys, time, threading, statistics

sys.setswitchinterval(0.005)          # значение по умолчанию

ev = threading.Event()
lat = []

def burner():                          # один поток занимает GIL
    x = 0
    while not ev.is_set():
        x += 1

def responder():                       # второй пытается быстро просыпаться
    for _ in range(80):
        t0 = time.perf_counter()
        time.sleep(0.001)
        lat.append((time.perf_counter() - t0 - 0.001) * 1000)

b = threading.Thread(target=burner, daemon=True); b.start()
r = threading.Thread(target=responder); r.start(); r.join()
ev.set()

print("медиана опоздания, мс: %.2f" % statistics.median(lat))
# → около 5 мс: ровно интервал переключения
# без потока-burner то же измерение даёт 0.09 мс

3Что из этого следует

Диаграмма — модель: она честно воспроизводит правило «GIL держит один поток, вытеснение по интервалу», но не является трассой реального планировщика. Числа рядом с ней и весь второй блок — настоящие замеры на CPython 3.11.15, Linux x86-64, 2 ядра. Различать эти два статуса — та же привычка, что и различать 🔒 и 🔧.