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

Как устроен GIL?

3.0 Senior🔥 111 комментариев
#Python Core#Асинхронность и многопоточность

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

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

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

Как устроен GIL (Global Interpreter Lock)

GIL — это мьютекс в CPython, который позволяет только одному потоку исполнять Python байт-код одновременно. Это классический вопрос на собеседованиях, и я объясню, как это работает и почему.

Суть проблемы

Примерно до версии 1.5, Python был однопоточным. Когда добавили многопоточность, создатели столкнулись с проблемой: управление памятью в CPython использует reference counting. Если несколько потоков одновременно изменяют счётчик ссылок объекта, это приводит к race condition и утечкам памяти.

Вместо того чтобы сделать каждый объект thread-safe (с собственным мьютексом), создали GIL — глобальный мьютекс на всю интерпретатор.

Как это работает

import threading
import time

def cpu_bound_task(n):
    total = 0
    for i in range(n):
        total += i
    return total

# С потоками GIL будет одним, поэтому медленнее
start = time.time()
thread1 = threading.Thread(target=cpu_bound_task, args=(100000000,))
thread2 = threading.Thread(target=cpu_bound_task, args=(100000000,))
thread1.start()
thread2.start()
thread1.join()
thread2.join()
print(f"Threading: {time.time() - start:.2f}s")

# Без потоков быстрее, потому что нет overhead переключения контекста
start = time.time()
cpu_bound_task(100000000)
cpu_bound_task(100000000)
print(f"Sequential: {time.time() - start:.2f}s")

В примере выше многопоточный вариант часто медленнее из-за GIL и overhead управления потоками.

Когда GIL НЕ мешает

GIL отпускается при блокирующих I/O операциях:

import threading
import requests

def fetch_url(url):
    response = requests.get(url)  # GIL отпускается здесь
    return response.status_code

# Многопоточность эффективна для I/O
threads = [
    threading.Thread(target=fetch_url, args=(url,))
    for url in ["https://api.com/1", "https://api.com/2"]
]
for t in threads:
    t.start()
for t in threads:
    t.join()

Здесь многопоточность работает эффективно, потому что потоки ждут сети, а не исполняют Python код.

Как я обхожу GIL

1. Асинхронное программирование (asyncio)

import asyncio

async def fetch(url):
    # Эмулирую HTTP запрос
    await asyncio.sleep(1)
    return f"Response from {url}"

async def main():
    tasks = [fetch(f"url{i}") for i in range(10)]
    results = await asyncio.gather(*tasks)
    print(results)

asyncio.run(main())

Это один поток, но he поддерживает concurrency. Я использую это в web приложениях (FastAPI, aiohttp).

2. Multiprocessing для CPU-bound задач

from multiprocessing import Pool

def cpu_bound(n):
    return sum(i for i in range(n))

if __name__ == "__main__":
    with Pool(4) as p:
        results = p.map(cpu_bound, [100000000] * 4)

Каждый процесс имеет собственный Python интерпретатор и свой GIL. Для вычислений — оптимальный выбор.

3. Внешние библиотеки

Много библиотек (NumPy, pandas) написаны на C и отпускают GIL при работе с данными.

В Python 3.13+

Текущее разработки PEP 703 предлагает полностью убрать GIL, но это требует переписания internals CPython. Это займёт годы.

Выводы: GIL — это компромисс. Он делает CPython простым и безопасным, но ограничивает многопоточность для CPU-bound задач. Знание его особенностей — критическое для правильного выбора модели concurrency в Python.

Как устроен GIL? | PrepBro