Что такое глобальная блокировка интерпретатора (GIL) в CPython?
Что такое глобальная блокировка интерпретатора и почему это проблема?
Было поднято много шума вокруг удаления GIL из Python, и я хотел бы понять, почему это так важно. Я сам никогда не писал ни компилятор, ни интерпретатор, поэтому не экономьте на деталях, они, вероятно, понадобятся мне для понимания.
Переведено автоматически
Ответ 1
GIL в Python предназначен для сериализации доступа к внутренним компонентам интерпретатора из разных потоков. В многоядерных системах это означает, что несколько потоков не могут эффективно использовать несколько ядер. (Если бы GIL не приводил к этой проблеме, большинству людей было бы наплевать на GIL - это только поднимается как проблема из-за растущей распространенности многоядерных систем.) Если вы хотите разобраться в этом подробнее, вы можете просмотреть это видео или посмотреть на этот набор слайдов. Возможно, информации слишком много, но тогда вы запросили подробности :-)
Обратите внимание, что GIL в Python на самом деле является проблемой только для 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 3.7
Я также хотел бы выделить следующую цитату из документацииthreading
Python:
Подробная информация о реализации CPython: В CPython из-за глобальной блокировки интерпретатора только один поток может выполнять код Python одновременно (хотя некоторые библиотеки, ориентированные на производительность, могут преодолеть это ограничение). Если вы хотите, чтобы ваше приложение лучше использовало вычислительные ресурсы многоядерных компьютеров, вам рекомендуется использовать
multiprocessing
илиconcurrent.futures.ProcessPoolExecutor
. Тем не менее, потоковая обработка по-прежнему является подходящей моделью, если вы хотите одновременно запускать несколько задач, связанных с вводом-выводом.
Это ссылка на запись глоссария для global interpreter lock
, в которой объясняется, что GIL подразумевает, что многопоточный параллелизм в Python непригоден для задач, связанных с ЦП:
Механизм, используемый интерпретатором CPython для обеспечения того, чтобы только один поток выполнял байт-код Python одновременно. Это упрощает реализацию CPython, делая объектную модель (включая критически важные встроенные типы, такие как dict ) неявно безопасной от параллельного доступа. Блокировка всего интерпретатора упрощает его многопоточность за счет большей части параллелизма, обеспечиваемого многопроцессорными машинами.
Однако некоторые модули расширения, стандартные или сторонних производителей, разработаны таким образом, чтобы освобождать GIL при выполнении задач, требующих больших вычислительных ресурсов, таких как сжатие или хеширование. Кроме того, GIL всегда освобождается при выполнении ввода-вывода.
Прошлые попытки создать ”свободнопоточный" интерпретатор (тот, который блокирует общие данные с гораздо более высокой степенью детализации) не увенчались успехом из-за снижения производительности в обычном однопроцессорном случае. Считается, что преодоление этой проблемы с производительностью значительно усложнило бы реализацию и, следовательно, удорожало бы обслуживание.
Эта цитата также подразумевает, что dicts и, следовательно, назначение переменных также потокобезопасны, как деталь реализации CPython:
Далее, в документах для multiprocessing
пакета объясняется, как он преодолевает GIL, создавая процесс, предоставляя интерфейс, аналогичный интерфейсу threading
:
многопроцессорность - это пакет, который поддерживает порождающие процессы с использованием API, аналогичного модулю threading. Многопроцессорный пакет предлагает как локальный, так и удаленный параллелизм, эффективно обходя глобальную блокировку интерпретатора за счет использования подпроцессов вместо потоков. Благодаря этому модуль многопроцессорной обработки позволяет программисту полностью использовать несколько процессоров на данной машине. Он работает как в Unix, так и в Windows.
And the docs for concurrent.futures.ProcessPoolExecutor
explain that it uses multiprocessing
as a backend:
The ProcessPoolExecutor class is an Executor subclass that uses a pool of processes to execute calls asynchronously. ProcessPoolExecutor uses the multiprocessing module, which allows it to side-step the Global Interpreter Lock but also means that only picklable objects can be executed and returned.
which should be contrasted to the other base class ThreadPoolExecutor
that uses threads instead of processes
ThreadPoolExecutor is an Executor subclass that uses a pool of threads to execute calls asynchronously.
from which we conclude that ThreadPoolExecutor
is only suitable for I/O bound tasks, while ProcessPoolExecutor
can also handle CPU bound tasks.
Process vs thread experiments
В Multiprocessing vs Threading Python я провел экспериментальный анализ процесса и потоков в Python.
Быстрый просмотр результатов:
В других языках
Концепция, похоже, существует и за пределами Python, с таким же успехом применяясь, например, к Ruby: https://en.wikipedia.org/wiki/Global_interpreter_lock
В нем упоминаются преимущества:
- повышена скорость работы однопоточных программ (нет необходимости приобретать или снимать блокировки для всех структур данных по отдельности),
- простая интеграция библиотек C, которые обычно не являются потокобезопасными,
- простота реализации (наличие единственного GIL реализовать намного проще, чем интерпретатор без блокировок или использующий мелкозернистые блокировки).
но JVM, похоже, прекрасно обходится и без GIL, поэтому я задаюсь вопросом, стоит ли оно того. Следующий вопрос задает вопрос, почему GIL существует в первую очередь: Почему глобальная блокировка интерпретатора?