Ответ
GIL (Global Interpreter Lock) — это мьютекс (блокировка) в реализации интерпретатора CPython, который позволяет выполняться только одному потоку Python в один момент времени, даже на многоядерных системах. Он существует потому, что управление памятью CPython (счетчик ссылок) не является потокобезопасным.
Основные последствия:
- CPU-bound задачи: Потоки Python не могут выполняться параллельно на нескольких ядрах CPU для чисто вычислительных задач. Это ограничивает производительность.
- I/O-bound задачи: GIL не является проблемой, так как он отпускается, когда поток ожидает завершения операций ввода-вывода (чтение файла, сетевой запрос). Поэтому потоки эффективны для задач, связанных с ожиданием.
Как обойти ограничение GIL:
- Мультипроцессинг (
multiprocessing): Запуск нескольких процессов Python, каждый со своим интерпретатором и GIL. Это позволяет использовать несколько ядер CPU. - Асинхронное программирование (
asyncio): Для высоконагруженных I/O-приложений, чтобы избегать блокировок вообще. - Использование C-расширений: Критичные к производительности части можно вынести в модули на C, где GIL можно временно отпустить.
- Использование других реализаций интерпретатора: Например, 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.