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

Для чего нужно отключать GIL?

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

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

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

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

Для чего нужно отключать GIL?

GIL (Global Interpreter Lock) — это мьютекс (блокировка) в CPython, которая позволяет только ОДНОМУ потоку выполнять Python код одновременно. Отключение GIL нужно для достижения настоящего параллелизма в многопоточных приложениях.

Что такое GIL?

# GIL — это глобальная блокировка
# Даже на многоядерном процессоре:

import threading

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

thread1 = threading.Thread(target=cpu_intensive)
thread2 = threading.Thread(target=cpu_intensive)

import time
start = time.time()

# Запускаем два потока одновременно
thread1.start()
thread2.start()
thread1.join()
thread2.join()

print(f"Два потока: {time.time() - start:.2f}s")
# Результат: примерно 10+ секунд (как один поток!)
# Потоки не работают параллельно из-за GIL

# А если однопоточно?
start = time.time()
cpu_intensive()
cpu_intensive()
print(f"Один поток: {time.time() - start:.2f}s")
# Результат: примерно 10 секунд (примерно то же!)

Почему GIL существует?

1. УПРАВЛЕНИЕ ПАМЯТЬЮ
   ├─ CPython использует reference counting
   ├─ Счётчик ссылок НЕ потокобезопасен
   └─ Нужна глобальная блокировка

2. СОВМЕСТИМОСТЬ
   ├─ Много C расширений рассчитаны на GIL
   ├─ Убрать GIL = сломать совместимость
   └─ Это был компромисс

3. ПРОИЗВОДИТЕЛЬНОСТЬ В ОДНОПОТОЧНОСТИ
   ├─ GIL делает однопоточные приложения быстрее
   ├─ Нет контенции на лок
   └─ Чистая выигрыш для большинства кода

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

# 1. CPU-BOUND многопоточность
import threading
import time

def calculate():
    return sum(i**2 for i in range(10_000_000))

start = time.time()

# ПЛОХО: два потока, но выполняются по очереди
threads = [threading.Thread(target=calculate) for _ in range(2)]
for t in threads:
    t.start()
for t in threads:
    t.join()

threads_time = time.time() - start
print(f"2 потока (CPU-bound): {threads_time:.2f}s")

# ХОРОШО: процессы НЕ имеют GIL
from multiprocessing import Process

start = time.time()

processes = [Process(target=calculate) for _ in range(2)]
for p in processes:
    p.start()
for p in processes:
    p.join()

processes_time = time.time() - start
print(f"2 процесса (CPU-bound): {processes_time:.2f}s")
print(f"Процессы быстрее в {threads_time / processes_time:.1f}x раз")

Почему NOT нужно отключать GIL (при I/O)

# Для I/O операций GIL ОТПУСКАЕТСЯ!

import threading
import time
import requests

def fetch_url():
    # Во время network I/O GIL отпускается
    requests.get("https://httpbin.org/delay/1")

start = time.time()

threads = [threading.Thread(target=fetch_url) for _ in range(10)]
for t in threads:
    t.start()
for t in threads:
    t.join()

print(f"10 потоков для I/O: {time.time() - start:.2f}s")
# Результат: примерно 1-2 секунды (параллельно!)
# GIL отпустился во время блокирующего I/O

PEP 703: Отключение GIL

Сэм Гросс предложил в 2023 году отключаемый GIL:

# Python 3.13+: --disable-gil флаг
# python --disable-gil script.py

import threading
import time

def cpu_bound():
    return sum(i**2 for i in range(100_000_000))

start = time.time()

threads = [threading.Thread(target=cpu_bound) for _ in range(4)]
for t in threads:
    t.start()
for t in threads:
    t.join()

print(f"4 потока (с GIL): 20+ секунд")
print(f"4 потока (без GIL): 5-6 секунд")
# С отключённым GIL потоки работают ПАРАЛЛЕЛЬНО!

Как Python отпускает GIL?

# 1. Во время блокирующего I/O
import socket

def network_io():
    sock = socket.socket()
    # GIL отпускается здесь!
    sock.connect(("example.com", 80))
    # GIL захватывается обратно

# 2. Функции NumPy работают БЕЗ GIL
import numpy as np

array = np.arange(10_000_000)
result = array.sum()  # GIL отпущен!

# 3. В C расширениях можно явно отпустить
# Py_BEGIN_ALLOW_THREADS
// C code
// Py_END_ALLOW_THREADS

Решения вместо отключения GIL

# 1. MULTIPROCESSING — для CPU-bound
from multiprocessing import Pool

def heavy_calculation(n):
    return sum(i**2 for i in range(n))

with Pool(4) as pool:
    results = pool.map(heavy_calculation, [10_000_000] * 4)
    # Работает параллельно, нет GIL

# 2. ASYNCIO — для I/O-bound (лучший выбор!)
import asyncio
import aiohttp

async def fetch_many():
    async with aiohttp.ClientSession() as session:
        tasks = []
        for url in urls:
            tasks.append(session.get(url))
        return await asyncio.gather(*tasks)

asyncio.run(fetch_many())
# Один поток, но тысячи параллельных I/O операций

# 3. NUMPY/PANDAS — для вычислений
import numpy as np
import pandas as pd

data = np.random.randn(10_000_000)
result = data.mean()  # GIL отпущен, работает быстро

df = pd.DataFrame({"col": range(10_000_000)})
df["squared"] = df["col"] ** 2  # Параллельно!

# 4. JYTHON или IRONPYTHON — альтернативные реализации
# Jython (Java) и IronPython (.NET) НЕ имеют GIL!
# Но они медленнее CPython в однопоточности

Сравнение подходов

# Задача: обработать 4 файла по 1GB каждый

# ❌ НЕПРАВИЛЬНО: потоки с GIL
import threading
threads = [threading.Thread(target=process_file, args=(f,)) for f in files]
for t in threads: t.start()
for t in threads: t.join()
# Медленно! Потоки по очереди

# ✅ ПРАВИЛЬНО: процессы
from multiprocessing import Pool
with Pool(4) as pool:
    pool.map(process_file, files)
# Быстро! Истинный параллелизм

# ✅ ПРАВИЛЬНО: async для I/O
import asyncio
async def async_process_files():
    tasks = [async_process_file(f) for f in files]
    await asyncio.gather(*tasks)
asyncio.run(async_process_files())
# Один процесс, много I/O параллельно

Python 3.13: Экспериментальное отключение GIL

# Установка с отключённым GIL
python3.13 --disable-gil script.py

# Или компиляция из исходников
./configure --disable-gil
make

Performance с отключённым GIL

# На Python 3.13 с --disable-gil

import threading
import time

def cpu_bound():
    return sum(i**2 for i in range(100_000_000))

start = time.time()
threads = [threading.Thread(target=cpu_bound) for _ in range(8)]
for t in threads: t.start()
for t in threads: t.join()

with_gil = time.time() - start  # ~30 сек

# python3.13 --disable-gil script.py
# без GIL: ~4 сек (8x ускорение)

Резюме

GIL нужно отключать когда:

  • Много CPU-bound операций в потоках
  • Нужен истинный параллелизм на многоядерном процессоре
  • Используется Python 3.13+

GIL НЕ мешает когда:

  • I/O-bound операции (сетевые запросы, файлы, БД)
  • NumPy/Pandas вычисления
  • AsyncIO приложения

Лучшие практики прямо сейчас:

  1. Используй asyncio для I/O-bound
  2. Используй multiprocessing для CPU-bound
  3. Используй NumPy/Pandas для вычислений (GIL отпускается)
  4. Жди Python 3.13+ и используй --disable-gil
Для чего нужно отключать GIL? | PrepBro