Что такое глобальная блокировка интерпретатора (GIL) в CPython?

Что такое глобальная блокировка интерпретатора и почему это проблема?

Много шума было устранено при удалении GIL из Python, и я хотел бы понять, почему это так важно. Я никогда не писал ни компилятор, ни интерпретатор, так что не стесняйтесь подробностей, мне, вероятно, понадобится их понять.

Ответ 1

Python GIL предназначен для сериализации доступа к внутренним компонентам интерпретатора из разных потоков. В многоядерных системах это означает, что несколько потоков не могут эффективно использовать несколько ядер. (Если бы GIL не привел к этой проблеме, большинство людей не заботились бы о GIL - он поднимался только как проблема из-за растущей распространенности многоядерных систем.) Если вы хотите понять это подробно, Вы можете просмотреть это видео или посмотреть этот набор слайдов. Это может быть слишком много информации, но тогда вы спросили детали :-)

Обратите внимание, что Python GIL действительно проблема только для CPython, эталонной реализации. У Jython и IronPython нет GIL. Как разработчик Python, вы обычно не сталкиваетесь с GIL, если не пишете расширение на C. Авторы расширений C должны освобождать GIL, когда их расширения блокируют ввод/вывод, чтобы другие потоки в процессе Python могли запускаться.

Ответ 2

Предположим, что у вас несколько потоков, которые не касаются друг друга. Они должны выполняться как можно более независимо. Если у вас есть "глобальная блокировка", которую вам нужно приобрести, чтобы (скажем) вызвать функцию, которая может оказаться узким местом. Вы можете не получить большую выгоду от наличия нескольких потоков в первую очередь.

Поставить это в реальную мировую аналогию: представьте себе 100 разработчиков, работающих в компании с единственной кружкой кофе. Большинство разработчиков потратили свое время, ожидая кофе вместо кодирования.

Ничто из этого не является специфичным для Python - я не знаю подробностей о том, что Python нуждался в GIL в первую очередь. Однако, надеюсь, это дало вам лучшее представление об общей концепции.

Ответ 3

Давайте сначала поймем, что обеспечивает Python GIL:

Любая операция/инструкция выполняется в интерпретаторе. GIL гарантирует, что переводчик удерживается одним потоком в определенный момент времени. А ваша программа на Python с несколькими потоками работает в одном интерпретаторе. В любой конкретный момент времени этот переводчик удерживается одним потоком. Это означает, что в любой момент времени работает только тот поток, который содержит интерпретатор.

Теперь, почему это проблема:

Ваша машина может иметь несколько ядер/процессоров. И несколько ядер позволяют выполнять несколько потоков одновременно, т.е. несколько потоков могут выполняться в любой конкретный момент времени. , Но поскольку интерпретатор удерживается одним потоком, другие потоки ничего не делают, даже если у них есть доступ к ядру. Таким образом, вы не получаете никаких преимуществ, предоставляемых несколькими ядрами, поскольку в любой момент используется только одно ядро, то есть ядро, используемое потоком, который в настоящее время содержит интерпретатор. Таким образом, ваша программа будет выполняться так же долго, как если бы это была однопоточная программа.

Однако потенциально блокирующие или длительные операции, такие как ввод-вывод, обработка изображений и сокращение числа NumPy, происходят вне GIL. Взято отсюда. Таким образом, для таких операций многопоточная операция все равно будет быстрее однопоточной, несмотря на наличие GIL. Таким образом, GIL не всегда является узким местом.

Изменение: GIL является деталью реализации CPython. IronPython и Jython не имеют GIL, поэтому в них должна быть по-настоящему многопоточная программа, хотя я никогда не использовал PyPy и Jython и не уверен в этом.

Ответ 4

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

https://www.youtube.com/watch?v=ph374fJqFPE

GIL гарантирует, что только один из ваших "потоков" может выполняться одновременно. Поток получает GIL, выполняет небольшую работу, а затем передает GIL следующему потоку. Это происходит очень быстро, поэтому человеческому глазу может показаться, что ваши потоки выполняются параллельно, но на самом деле они просто по очереди используют одно и то же ядро ЦП. Вся эта передача GIL увеличивает накладные расходы на выполнение. Это означает, что если вы хотите, чтобы ваш код работал быстрее, то использование потокового пакета часто не является хорошей идеей.

Есть причины использовать пакет потоков Python. Если вы хотите запускать некоторые вещи одновременно, а эффективность не имеет значения, тогда это совершенно нормально и удобно. Или, если вы запускаете код, который должен чего-то ждать (например, какой-нибудь ввод-вывод), тогда это может иметь большой смысл. Но библиотека потоков не позволит вам использовать дополнительные ядра процессора.

Многопоточность может быть передана на аутсорсинг операционной системе (посредством многопроцессорной обработки), некоторому внешнему приложению, которое вызывает ваш код Python (например, Spark или Hadoop), или некоторому коду, который вызывает ваш код Python (например, у вас может быть ваш Python код вызывает функцию C, которая делает дорогие многопоточные вещи).

Ответ 5

Всякий раз, когда два потока имеют доступ к одной и той же переменной, у вас есть проблема. Например, в С++ способ избежать проблемы состоит в том, чтобы определить какую-либо блокировку мьютекса, чтобы предотвратить, чтобы, например, два потока, чтобы вводить объект установки в одно и то же время.

Многопоточность возможна в python, но два потока не могут быть выполнены одновременно при гранулярности более тонкой, чем одна инструкция python. Текущая нить получает глобальный замок под названием GIL.

Это означает, что если вы начнете писать многопотоковый код, чтобы воспользоваться преимуществами вашего многоядерного процессора, ваша производительность не улучшится. Обычное обходное решение состоит из многопроцессного перехода.

Обратите внимание, что вы можете освободить GIL, если вы находитесь внутри метода, который вы написали в C, например.

Использование GIL не присуще Python, а некоторому его интерпретатору, включая наиболее распространенный CPython. (#edited, см. комментарий)

Проблема с GIL остается актуальной в Python 3000.

Ответ 9

Документация по Python 3.7

Я также хотел бы выделить следующую цитату из документации по threading Python:

Детали реализации CPython: В CPython из-за Глобальной блокировки интерпретатора только один поток может выполнить код Python одновременно (даже если некоторые ориентированные на производительность библиотеки могут преодолеть это ограничение). Если вы хотите, чтобы ваше приложение лучше использовало вычислительные ресурсы многоядерных машин, рекомендуется использовать multiprocessing или concurrent.futures.ProcessPoolExecutor. Тем не менее, многопоточность по-прежнему является подходящей моделью, если вы хотите запускать несколько задач, связанных с вводом/выводом одновременно.

Это ссылка на запись Глоссария для global interpreter lock которая объясняет, что GIL подразумевает, что многопоточный параллелизм в Python не подходит для задач, связанных с процессором:

Механизм, используемый интерпретатором CPython для гарантии того, что только один поток одновременно выполняет байт-код Python. Это упрощает реализацию CPython, делая объектную модель (включая критические встроенные типы, такие как dict) неявно защищенной от одновременного доступа. Блокировка всего интерпретатора облегчает многопоточность интерпретатора за счет большей части параллелизма, обеспечиваемого многопроцессорными машинами.

Однако некоторые модули расширения, как стандартные, так и сторонние, спроектированы так, чтобы высвобождать GIL при выполнении сложных вычислительных задач, таких как сжатие или хеширование. Кроме того, GIL всегда освобождается при выполнении ввода/вывода.

Предыдущие попытки создать интерпретатор с "свободной резьбой" (который блокирует общие данные с более тонкой степенью детализации) не увенчались успехом, поскольку в обычном случае с одним процессором пострадала производительность. Считается, что преодоление этой проблемы производительности сделает реализацию намного более сложной и, следовательно, более дорогой в обслуживании.

Эта цитата также подразумевает, что dict и, следовательно, присваивание также являются потокобезопасными в CPython:

Далее, документы для multiprocessing пакета объясняют, как он преодолевает GIL, порождая процесс, открывая интерфейс, похожий на интерфейс threading:

мультипроцессинг - это пакет, который поддерживает процессы порождения с использованием API, аналогичного модулю потоков. Многопроцессорный пакет предлагает как локальный, так и удаленный параллелизм, эффективно обходя блокировку глобального интерпретатора, используя подпроцессы вместо потоков. Благодаря этому многопроцессорный модуль позволяет программисту полностью использовать несколько процессоров на данном компьютере. Он работает как на Unix, так и на Windows.

И документы для concurrent.futures.ProcessPoolExecutor объясняют, что он использует multiprocessing в качестве бэкэнда:

Класс ProcessPoolExecutor является подклассом Executor, который использует пул процессов для асинхронного выполнения вызовов. ProcessPoolExecutor использует многопроцессорный модуль, который позволяет ему обойти блокировку глобальной интерпретации, но также означает, что только выбираемые объекты могут быть выполнены и возвращены.

который должен быть противопоставлен другому базовому классу ThreadPoolExecutor который использует потоки вместо процессов

ThreadPoolExecutor - это подкласс Executor, который использует пул потоков для асинхронного выполнения вызовов.

из чего мы заключаем, что ThreadPoolExecutor подходит только для задач, связанных с ProcessPoolExecutor/выводом, в то время как ProcessPoolExecutor также может обрабатывать задачи, связанные с процессором.

Следующий вопрос спрашивает, почему GIL существует в первую очередь: почему Global Interpreter Lock?

Процесс против потока экспериментов

В Multiprocessing vs Threading Python я провел экспериментальный анализ процессов против потоков в Python.

Быстрый предварительный просмотр результатов:

enter image description here

Ответ 12

Почему Python (CPython и другие) использует GIL

Из http://wiki.python.org/moin/GlobalInterpreterLock

В CPython блокировка глобального интерпретатора или GIL - это мьютекс, который предотвращает одновременное выполнение несколькими собственными потоками байт-кодов Python. Эта блокировка необходима главным образом потому, что управление памятью CPython не является потокобезопасным.

Как удалить его из Python?

Как и Lua, возможно, Python может запускать несколько VM, но python этого не делает, я думаю, что должны быть и другие причины.

В Numpy или какой-либо другой расширенной библиотеке python, иногда, освобождение GIL для других потоков может повысить эффективность всей программы.

Ответ 13

Я хочу поделиться примером из многопоточной книги для визуальных эффектов. Итак, вот классическая ситуация с мертвой блокировкой.

static void MyCallback(const Context &context){
Auto<Lock> lock(GetMyMutexFromContext(context));
...
EvalMyPythonString(str); //A function that takes the GIL
...    
}

Теперь рассмотрим события в последовательности, получившие мертвую блокировку.

╔═══╦════════════════════════════════════════╦══════════════════════════════════════╗
║   ║ Main Thread                            ║ Other Thread                         ║
╠═══╬════════════════════════════════════════╬══════════════════════════════════════╣
║ 1 ║ Python Command acquires GIL            ║ Work started                         ║
║ 2 ║ Computation requested                  ║ MyCallback runs and acquires MyMutex ║
║ 3 ║                                        ║ MyCallback now waits for GIL         ║
║ 4 ║ MyCallback runs and waits for MyMutex  ║ waiting for GIL                      ║
╚═══╩════════════════════════════════════════╩══════════════════════════════════════╝