Что такое GIL (Global Interpreter Lock) в Python?

«Что такое GIL (Global Interpreter Lock) в Python?» — вопрос из категории Python, который задают на 33% собеседований Data Инженер. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

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

Основные последствия:

  • CPU-bound задачи: Потоки Python не могут выполняться параллельно на нескольких ядрах CPU для чисто вычислительных задач. Это ограничивает производительность.
  • I/O-bound задачи: GIL не является проблемой, так как он отпускается, когда поток ожидает завершения операций ввода-вывода (чтение файла, сетевой запрос). Поэтому потоки эффективны для задач, связанных с ожиданием.

Как обойти ограничение GIL:

  1. Мультипроцессинг (multiprocessing): Запуск нескольких процессов Python, каждый со своим интерпретатором и GIL. Это позволяет использовать несколько ядер CPU.
  2. Асинхронное программирование (asyncio): Для высоконагруженных I/O-приложений, чтобы избегать блокировок вообще.
  3. Использование C-расширений: Критичные к производительности части можно вынести в модули на C, где GIL можно временно отпустить.
  4. Использование других реализаций интерпретатора: Например, Jython или IronPython не имеют GIL, но они менее распространены.

Пример, демонстрирующий ограничение для CPU-bound задач:

import threading
import time

def cpu_bound_task():
    count = 0
    for _ in range(10_000_000):
        count += 1

# Последовательное выполнение
start = time.time()
cpu_bound_task()
cpu_bound_task()
print(f"Последовательно: {time.time() - start:.2f} сек")

# Параллельное выполнение в потоках (GIL мешает)
start = time.time()
thread1 = threading.Thread(target=cpu_bound_task)
thread2 = threading.Thread(target=cpu_bound_task)
thread1.start()
thread2.start()
thread1.join()
thread2.join()
print(f"В потоках: {time.time() - start:.2f} сек")  # Время будет примерно таким же или больше

Для истинного параллелизма CPU-bound задач следует использовать multiprocessing.