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

StackOverflow https://stackoverflow.com/questions/1294382

  •  18-09-2019
  •  | 
  •  

Вопрос

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

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

Это было полезно?

Решение

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

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

Другие советы

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

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

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

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

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

Итак, почему это проблема:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Подробная информация о реализации 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, аналогичного модулю threading.Многопроцессорный пакет обеспечивает как локальный, так и удаленный параллелизм, эффективно обходя Глобальную блокировку интерпретатора за счет использования подпроцессов вместо потоков.Благодаря этому модуль многопроцессорной обработки позволяет программисту полностью использовать несколько процессоров на данной машине.Он работает как в Unix, так и в Windows.

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

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

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

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

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

Следующий вопрос задает вопрос, почему GIL существует в первую очередь: Почему Глобальная блокировка интерпретатора?

Эксперименты с процессом и потоком

В Многопроцессорный против Многопоточного Python Я провел экспериментальный анализ процесса и потоков в Python.

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

enter image description here

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

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

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

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

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

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

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

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                      ║
╚═══╩════════════════════════════════════════╩══════════════════════════════════════╝
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top