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

Почему GIL не позволяет реализовать настоящую многопоточность (multithreading) в Python?

1.0 Junior🔥 161 комментариев
#Асинхронность и многопоточность

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

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

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

Ответ: GIL это Global Interpreter Lock

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

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

Python объекты требуют управления памятью через подсчёт ссылок (reference counting). Без GIL пришлось бы:

  1. Защищать каждый объект своим мьютексом (сложно)
  2. Риск deadlock-ов (высокий)
  3. Огромное замедление (каждый доступ — блокировка)

GIL решает это просто: один глобальный мьютекс для всего интерпретатора.

# Пример того, почему нужна синхронизация:
# Если два потока одновременно увеличат счётчик ссылок:

# Поток 1: obj.ref_count += 1
# Поток 2: obj.ref_count += 1
# Результат: increment только на 1, вместо 2 (race condition)

# GIL гарантирует, что это не случится

Как GIL ломает многопоточность

Проблема 1: Только один поток за раз

import threading
import time

def cpu_bound_work():
    """CPU-интенсивная работа"""
    total = 0
    for i in range(100_000_000):
        total += i
    return total

def single_thread():
    """Один поток — два вызова"""
    start = time.time()
    cpu_bound_work()
    cpu_bound_work()
    return time.time() - start

def multi_thread():
    """Два потока одновременно"""
    start = time.time()
    
    t1 = threading.Thread(target=cpu_bound_work)
    t2 = threading.Thread(target=cpu_bound_work)
    
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    
    return time.time() - start

print(f"Single thread: {single_thread():.2f}s")  # ~5.2s
print(f"Multi thread:  {multi_thread():.2f}s")   # ~5.5s (даже медленнее!)
# Два потока медленнее одного — из-за overhead GIL

Проблема 2: GIL выпускается во время I/O

import threading
import time
import requests

def fetch_url():
    """I/O операция (GIL выпускается)"""
    # Здесь GIL отпускается — другой поток может работать
    requests.get("https://api.example.com")

def single_io():
    start = time.time()
    fetch_url()
    fetch_url()
    return time.time() - start  # ~4 секунды (два запроса подряд)

def multi_io():
    start = time.time()
    t1 = threading.Thread(target=fetch_url)
    t2 = threading.Thread(target=fetch_url)
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    return time.time() - start  # ~2 секунды (параллельно!)

print(f"Single: {single_io():.1f}s")
print(f"Multi:  {multi_io():.1f}s")

Вывод: Threading работает для I/O, не для CPU.

Когда GIL отпускается

  1. Во время I/O операций: socket, file, requests, database
  2. В numpy/C расширениях: код на C обходит GIL
  3. В time.sleep(): поток спит

В эти моменты другие потоки могут работать.

Решения

1. multiprocessing — для CPU-bound

from multiprocessing import Pool

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

if __name__ == "__main__":
    # Каждый процесс имеет свой интерпретатор и GIL
    with Pool(4) as p:
        results = p.map(cpu_work, [100_000_000] * 4)
    print(results)

2. asyncio — для I/O-bound

import asyncio
import aiohttp

async def fetch_many():
    async with aiohttp.ClientSession() as session:
        # Кооперативная многозадачность без потоков
        tasks = [
            session.get(f"https://api.example.com/{i}")
            for i in range(100)
        ]
        return await asyncio.gather(*tasks)

asyncio.run(fetch_many())

3. C расширения — NumPy, Pandas

import numpy as np

# NumPy операции выпускают GIL
arr = np.arange(100_000_000)
result = np.sum(arr)  # Это работает параллельно на многоядрах!

4. Python 3.13+ — Free-threading (экспериментально)

В новых версиях Python начинают убирать GIL, но пока это не стабильно.

Таблица: Когда использовать что

Тип работыРешениеОписание
CPU-boundmultiprocessingОтдельные процессы, каждый со своим GIL
I/O-bound (сеть)asyncioКооперативная многозадачность
I/O-bound (файлы)threading + concurrent.futuresGIL отпускается на файловых операциях
NumPy вычисленияВстроенные функцииC расширения выпускают GIL

Итог

  • GIL существует для безопасного управления памятью в CPython
  • Он ломает threading для CPU-bound задач
  • Для CPU: используй multiprocessing
  • Для I/O: используй asyncio или threading
  • Помни: GIL — это реальность CPython, с этим нужно работать, а не бороться
Почему GIL не позволяет реализовать настоящую многопоточность (multithreading) в Python? | PrepBro