← Назад к вопросам

Когда GIL отдаёт управление?

2.0 Middle🔥 131 комментариев
#DevOps и инфраструктура

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

GIL (Global Interpreter Lock) — когда отдаёт управление

GIL в Python отдаёт управление другим потокам в трёх основных случаях:

1. После выполнения определённого количества инструкций

По умолчанию GIL переключается каждые 100 инструкций Python (в Python 3.13+ эта поведение изменилось).

import sys

# Просмотр текущего лимита
print(sys.getcheckinterval())  # Было 100 в Python < 3.2

# В Python 3.2+ используется sys.setswitchinterval()
print(sys.getswitchinterval())  # По умолчанию 0.005 сек (5 мс)

# Изменение интервала
sys.setswitchinterval(0.001)  # Чаще переключаться

2. При блокирующих I/O операциях

Это самый важный случай — GIL отдаётся на время I/O:

import threading
import time
import requests

def fetch_data(url):
    print(f"Поток {threading.current_thread().name} начал запрос")
    # GIL ОТДАН на время HTTP запроса!
    response = requests.get(url)
    print(f"Поток {threading.current_thread().name} получил ответ")
    return response

# Два потока могут работать параллельно при I/O
t1 = threading.Thread(target=fetch_data, args=("http://example.com",))
t2 = threading.Thread(target=fetch_data, args=("http://google.com",))

t1.start()
t2.start()
t1.join()
t2.join()
# Обе операции выполнились почти одновременно благодаря GIL

Примеры блокирующих операций:

  • socket.recv() — получение данных из сети
  • file.read() — чтение файла
  • time.sleep() — пауза в потоке
  • subprocess.call() — запуск процесса
  • Все операции с сетью (requests, urllib и т.д.)

3. При явном освобождении в C расширениях

Велики могут явно освобождать GIL в C коде через Py_BEGIN_ALLOW_THREADS и Py_END_ALLOW_THREADS:

// NumPy операции работают параллельно
// потому что освобождают GIL
import numpy as np
import threading

def matrix_mult(matrix):
    # GIL отдан на время numpy операции!
    return np.dot(matrix, matrix.T)

# Можно использовать потоки для параллельных вычислений

Проблема: CPU-bound операции

Для вычислительно интенсивных операций GIL НЕ отдаётся:

import threading
import time

def cpu_intensive():
    total = 0
    for i in range(100_000_000):
        total += i
    return total

# Способ 1: Threading (ПЛОХО для CPU-bound)
start = time.time()
t1 = threading.Thread(target=cpu_intensive)
t2 = threading.Thread(target=cpu_intensive)
t1.start()
t2.start()
t1.join()
t2.join()
thread_time = time.time() - start
print(f"Threading: {thread_time}s")  # ~2s (один поток выполняет)

# Способ 2: Multiprocessing (ХОРОШО для CPU-bound)
from multiprocessing import Process

start = time.time()
p1 = Process(target=cpu_intensive)
p2 = Process(target=cpu_intensive)
p1.start()
p2.start()
p1.join()
p2.join()
process_time = time.time() - start
print(f"Multiprocessing: {process_time}s")  # ~1s (реально параллельно)

Python 3.13+ — устранение GIL

В Python 3.13 начал вводиться режим без GIL (когда включён флаг --disable-gil):

python3.13 --disable-gil my_script.py

Это позволяет потокам работать по-настоящему параллельно.

Резюме: Когда GIL отдаёт управление

СитуацияGIL отдан?Пример
I/O операция✅ ДАrequests.get(), socket.recv()
time.sleep()✅ ДАПауза между запросами
Чтение/запись файлов✅ ДАfile.read(), file.write()
CPU-bound вычисления❌ НЕТЦиклы, обработка данных
NumPy/SciPy операции✅ ДАnp.dot(), scipy.optimize

Практические рекомендации

Для I/O-bound задач:

import asyncio

# Асинхронность лучше для I/O
async def fetch_many():
    tasks = [asyncio.sleep(1) for _ in range(100)]
    await asyncio.gather(*tasks)

Для CPU-bound задач:

from concurrent.futures import ProcessPoolExecutor

# Multiprocessing обходит GIL
with ProcessPoolExecutor(max_workers=4) as executor:
    results = list(executor.map(cpu_intensive, range(4)))

ГIL отдаётся автоматически при I/O и принудительно через интервалы времени, но не помогает при вычислениях.

Когда GIL отдаёт управление? | PrepBro