11.1 Global Interpreter Lock
Global Interpreter Lock (GIL) — это механизм в интерпретаторе CPython, который обеспечивает, что только один поток выполняет байт-код Python в любое время. GIL предотвращает параллельное выполнение потоков, что может негативно влиять на производительность многопоточных программ, особенно на многоядерных процессорах.
Причины существования GIL
Упрощение управления памятью: GIL упрощает управление памятью и сборку мусора, делая Python проще для реализации.
Потокобезопасность: GIL предотвращает состояния гонки, делая выполнение кода потокобезопасным без необходимости использовать блокировки.
Проблемы, вызванные GIL
Ограниченная производительность: Многопоточные программы, выполняющие вычислительно интенсивные задачи, не могут полностью использовать преимущества многоядерных процессоров из-за ограничений GIL.
Искажение производительности: Программы, которые интенсивно используют потоки для выполнения задач, могут столкнуться с ухудшением производительности из-за переключения контекста между потоками.
Сейчас существует 4 основных способа «обхода GIL»:
11.2 Использование многопроцессорности (multiprocessing)
Модуль multiprocessing позволяет создавать процессы, которые выполняются параллельно и не ограничены GIL, так как каждый процесс имеет свой собственный интерпретатор Python и память.
Пример:
import multiprocessing
def worker(num):
print(f'Worker: {num}')
def main():
processes = []
for i in range(5):
p = multiprocessing.Process(target=worker, args=(i,))
processes.append(p)
p.start()
for p in processes:
p.join()
main()
11.3 Асинхронное программирование
Асинхронное программирование с использованием asyncio позволяет выполнять задачи параллельно, не блокируя основной поток. Хотя это не обходит GIL в прямом смысле, оно позволяет эффективно использовать время ожидания для выполнения других задач.
Пример:
import asyncio
async def main():
await asyncio.sleep(1)
print('Hello')
asyncio.run(main())
11.4 Использование библиотек с собственным управлением потоками
Некоторые библиотеки, такие как NumPy и SciPy, написаны на C и используют собственные механизмы управления потоками, что позволяет им обходить GIL и эффективно использовать многоядерные процессоры для вычислений.
Собственно, это и есть одна из основных причин успеха Python, какой бы медленный он ни был. Все сложные вычисления переписаны на языках C/C++ и выполняются на всех ядрах процессора или даже сразу на ядрах видеокарты. А на современных видеокартах тысячи ядер.
Получается, что уже не важно, насколько язык быстрый или медленный, если все ресурсоемкие вычисления выполняются внешними библиотеками или вообще вынесены в удаленные дата-центры.
11.5 Выполнение вычислений вне интерпретатора Python
Использование расширений на C/C++ или других языках, которые могут выполнять вычисления вне интерпретатора Python и затем возвращать результат. Это позволяет избежать блокировки GIL на время выполнения интенсивных вычислений.
Пример использования Cython:
# example.pyx
def compute(int n):
cdef int i, result = 0
for i in range(n):
result += i
return result
Компиляция и использование:
cythonize -i example.pyx
import example
print(example.compute(1000000))
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ