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


244

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

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


3
Посмотрите, как Дэвид Бизли расскажет вам все, что вы когда-либо хотели знать о GIL.
hughdbrown

1
Вот длинная статья, рассказывающая о GIL и потоках в Python, которую я написал некоторое время назад. В нем много подробностей: jessenoller.com/2009/02/01/…
jnoller

Вот некоторый код, демонстрирующий эффекты GIL: github.com/cankav/python_gil_demonstration
Can Kavaklıoğlu

3
Я считаю, что это лучшее объяснение GIL. Пожалуйста прочти. dabeaz.com/python/UnderstandingGIL.pdf
suhao399

realpython.com/python-gil Я нашел это полезным
qwr

Ответы:


220

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

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


46
Хороший ответ - в основном это означает, что потоки в Python хороши только для блокировки ввода / вывода; Ваше приложение никогда не превысит 1 процессорное ядро ​​использования процессора
Ана Беттс

8
«Как разработчик Python, вы, как правило, не сталкиваетесь с GIL, если не пишете расширение C» - вы можете не знать, что причиной того, что ваш многопоточный код работает со скоростью улитки, является GIL, но вы Я непременно почувствую его влияние. Меня по-прежнему поражает, что использование 32-ядерного сервера с Python означает, что мне нужно 32 процесса со всеми дополнительными издержками.
Basic

6
@PaulBetts: это не правда. Вполне вероятно , что производительность критически код уже использует расширения C , которые могут и не выпустить Gil , например, regex, lxml, numpyмодули. Cython позволяет выпускать GIL в пользовательском коде, например,b2a_bin(data)
jfs

5
@Paul Betts: Вы можете получить более 1 кода процессора, используя процессор, используя многопроцессорный модуль. Создание нескольких процессов «тяжелее», чем создание нескольких потоков, но если вам действительно нужно выполнять работу параллельно, в Python это вариант.
AJNeufeld

1
@david_adler Да, все еще так, и, вероятно, так будет еще некоторое время. Это на самом деле не помешало Python быть действительно полезным для множества различных рабочих нагрузок.
Vinay Sajip

59

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

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

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


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


36

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

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

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

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

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

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


4
Примечание : PyPy имеет GIL . Ссылка : http://doc.pypy.org/en/latest/faq.html#does-pypy-have-a-gil-why . В то время как у Ironpython и Jython нет GIL.
Тасдик Рахман

Действительно, у PyPy есть GIL, но у IronPython нет.
Эммануэль

@Emmanuel Отредактировал ответ, чтобы удалить PyPy и включить IronPython.
Акшар Раадж

17

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

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

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

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

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


15

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

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

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

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

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

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


Stackless по-прежнему имеет GIL. Stackless не улучшает многопоточность (как в модуле) - он предлагает другой метод программирования (сопрограммы), который пытается обойти проблему, но требует неблокирующих функций.
jnoller

Как насчет нового GIL в 3.2?
new123456

Просто добавьте, что у вас нет проблем / нужны мьютексы / семафоры, если только один поток обновит память. @ new123456 это уменьшает конкуренцию и улучшает планирование потоков без ущерба для однопоточной производительности (что само по себе впечатляет), но это все еще глобальная блокировка.
Основное

14

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

введите описание изображения здесь


0

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

От http://wiki.python.org/moin/GlobalInterpreterLock

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

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

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

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


0

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

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                      
╚═══╩════════════════════════════════════════╩══════════════════════════════════════╝
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.