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

Блокирует ли GIL процессы в Python?

2.0 Middle🔥 121 комментариев
#Python Core#Асинхронность и многопоточность

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

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

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

# GIL и его влияние на процессы в Python

Это частый вопрос, и ответ очень важен: GIL НЕ блокирует процессы, потому что процессы и потоки — это совершенно разные вещи.

Что такое GIL

GIL (Global Interpreter Lock) — это мьютекс в Python интерпретаторе CPython, который предотвращает одновременное выполнение Python кода несколькими потоками в одном процессе.

Потоки vs Процессы

Потоки (Threads) — ДА, блокируются GIL

Все потоки в одном процессе используют один интерпретатор Python и один GIL:

import threading
import time

def cpu_work(n):
    """CPU-bound операция."""
    total = 0
    for i in range(n):
        total += i * i
    return total

start = time.time()

# Два потока пытаются вычислять одновременно
thread1 = threading.Thread(target=cpu_work, args=(100_000_000,))
thread2 = threading.Thread(target=cpu_work, args=(100_000_000,))

thread1.start()
thread2.start()
thread1.join()
thread2.join()

print(f"С потоками: {time.time() - start:.2f}с")

# Для сравнения — последовательное выполнение
start = time.time()
cpu_work(100_000_000)
cpu_work(100_000_000)
print(f"Последовательно: {time.time() - start:.2f}s")

Вывод (на 4-ядерном процессоре):

С потоками: 19.45s        ← Потоки МЕДЛЕННЕЕ из-за GIL!
Последовательно: 18.32s   ← Даже последовательно быстрее

Почему медленнее? Потому что GIL постоянно переключается между потоками, добавляя overhead.

Процессы (Processes) — НЕТ, GIL не блокирует

Если использовать multiprocessing, каждый процесс имеет свой собственный Python интерпретатор и свой GIL:

from multiprocessing import Process
import time

def cpu_work(n):
    """CPU-bound операция."""
    total = 0
    for i in range(n):
        total += i * i
    return total

start = time.time()

# Два процесса работают полностью независимо
p1 = Process(target=cpu_work, args=(100_000_000,))
p2 = Process(target=cpu_work, args=(100_000_000,))

p1.start()
p2.start()
p1.join()
p2.join()

print(f"С процессами: {time.time() - start:.2f}s")

Вывод (на 4-ядерном процессоре):

С процессами: 9.50s   ← ЗНАЧИТЕЛЬНО быстрее!

Почему быстрее? Потому что:

  1. Каждый процесс работает на отдельном ядре CPU
  2. Каждый процесс имеет свой GIL
  3. Нет переключения контекста между потоками

Архитектура GIL

Процесс A (Python)
┌────────────────────────────┐
│ GIL (Global Lock)          │
│ ┌────────────────────────┐ │
│ │ Python код             │ │ ← Только ОДИН поток может выполняться
│ │ CPython VM             │ │    в одно время
│ └────────────────────────┘ │
│ Поток 1, Поток 2, Поток 3  │
└────────────────────────────┘

Процесс B (Python) — НЕЗАВИСИМЫЙ
┌────────────────────────────┐
│ GIL (свой, отдельный)      │
│ ┌────────────────────────┐ │
│ │ Python код             │ │ ← Полностью независимый
│ │ CPython VM             │ │    интерпретатор
│ └────────────────────────┘ │
│ Поток 1, Поток 2           │
└────────────────────────────┘

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

СитуацияПотокиПроцессыПочему
I/O-bound (сеть, файлы)✅ Отлично❌ ОverkillGIL освобождается при I/O, потоки эффективны
CPU-bound (вычисления)❌ Плохо✅ ОтличноGIL блокирует, процессы обходят это
Малая нагрузка✅ Просто❌ СложноватоПроцессы имеют overhead
Большая нагрузка❌ Медленно✅ БыстроПроцессы масштабируются лучше
Общая память✅ Легко❌ СложноПотоки видят одну память
Изоляция❌ Нет✅ ПолнаяПроцессы полностью независимы

Когда GIL не работает

GIL освобождается при:

  1. I/O операциях (сетевые запросы, файлы)
  2. Вызовах C расширений, если они это поддерживают
  3. NumPy операциях (они написаны на C)
import threading
import requests
import time

def fetch_url(url):
    """I/O-bound операция — GIL освобождается!"""
    response = requests.get(url)
    return len(response.content)

start = time.time()

urls = ["https://example.com"] * 5

# С потоками I/O-bound операции работают хорошо
threads = [threading.Thread(target=fetch_url, args=(url,)) for url in urls]
for t in threads:
    t.start()
for t in threads:
    t.join()

print(f"Время с потоками: {time.time() - start:.2f}s")

# Последовательно было бы намного медленнее

Вывод:

Время с потоками: 2.5s    ← Потоки работают параллельно, быстро

Пример: когда процессы спасают

import threading
from multiprocessing import Pool
import time

def heavy_computation(n):
    """Тяжелая CPU операция."""
    total = 0
    for i in range(n):
        total += i * i
    return total

data = [50_000_000] * 4

# ❌ Потоки НЕ помогут
start = time.time()
threads = []
for item in data:
    t = threading.Thread(target=heavy_computation, args=(item,))
    t.start()
    threads.append(t)
for t in threads:
    t.join()
thread_time = time.time() - start

# ✅ Процессы помогут
start = time.time()
with Pool(4) as pool:
    pool.map(heavy_computation, data)
process_time = time.time() - start

print(f"Потоки: {thread_time:.2f}s")
print(f"Процессы: {process_time:.2f}s")
print(f"Ускорение: {thread_time/process_time:.1f}x")

Вывод:

Потоки: 45.23s
Процессы: 12.45s
Ускорение: 3.6x

Альтернативы GIL в Python

  1. PyPy — другой интерпретатор, имеет GIL, но лучше оптимизирует
  2. Jython — работает на JVM, нет GIL
  3. IronPython — работает на .NET, нет GIL
  4. asyncio — асинхронное программирование, не требует потоков
  5. multiprocessing — обходит GIL полностью
  6. Python 3.13+ — экспериментальный режим "nogil" (для будущего)

Лучшая практика

import asyncio
import concurrent.futures
from multiprocessing import Pool

# 1. I/O-bound → asyncio (современный способ)
async def fetch_many():
    tasks = [fetch_url(url) for url in urls]
    await asyncio.gather(*tasks)

# 2. I/O-bound → threading (если asyncio сложно)
with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
    futures = [executor.submit(fetch_url, url) for url in urls]
    results = [f.result() for f in futures]

# 3. CPU-bound → multiprocessing (обходит GIL)
with concurrent.futures.ProcessPoolExecutor(max_workers=4) as executor:
    futures = [executor.submit(cpu_work, n) for n in data]
    results = [f.result() for f in futures]

# 4. CPU-bound large scale → multiprocessing.Pool
with Pool(4) as pool:
    results = pool.map(cpu_work, data)

Резюме

  1. GIL блокирует потоки, но НЕ блокирует процессы
  2. Потоки хороши для I/O-bound операций (сеть, файлы)
  3. Процессы необходимы для CPU-bound операций (вычисления)
  4. GIL освобождается при I/O, поэтому там потоки работают отлично
  5. Нет GIL в Jython и IronPython, но они не так популярны
  6. asyncio — современный способ для I/O-bound без потоков

Выбирайте инструмент в зависимости от типа работы, которую делает ваше приложение.

Блокирует ли GIL процессы в Python? | PrepBro