Global Interpreter Lock (GIL) в Python
Global Interpreter Lock (GIL) в Python был представлен, чтобы сделать обработку памяти CPython проще и обеспечить наилучшую интеграцию с языком C (например, с расширениями).
GIL — это механизм блокировки (mutex), которая не позволяет нескольким потокам выполнить один и тот же байткод. Эта блокировка, к сожалению, является необходимой, так как система управления памятью в CPython не является потокобезопасной.
Краткие сведения о GIL:
- Одновременно может выполняться только один поток.
- Интерпретатор Python переключается между потоками для достижения конкурентности.
- GIL применим к CPython (стандартной реализации). Но такие как, например, Jython и IronPython не имеют GIL.
- GIL делает однопоточные программы быстрыми.
- Операциям ввода/вывода, обычно GIL не мешает.
- GIL позволяет легко интегрировать непотокобезопасные библиотеки на языке C
- Благодаря GIL есть много высокопроизводительных расширений/модулей, написанных на языке C.
Для CPU зависимых задач интерпретатор делает проверку каждые N тиков и переключает потоки. Таким образом один поток не блокирует другие.
GIL это не проблема языка Python, а проблема реализации интерпретатора CPython. К счастью, в настоящее время существует несколько способов решения проблем GIL.
Многие видят слабость в GIL. Хотя это можно рассматривать это как благо, ведь были созданы такие библиотеки как | NumPy |, | Pandas |, которые занимают особое, уникальное положение в научном обществе.
GIL для каждого интерпретатора
PEP 684 вводит GIL для каждого интерпретатора, так что теперь можно создавать вспомогательные интерпретаторы с уникальным GIL для каждого интерпретатора. Это позволяет программам на Python в полной мере использовать преимущества нескольких процессорных ядер. В настоящее время это доступно только через C-API, хотя ожидается, что Python API появится в версии 3.13.
Для создания интерпретатора с собственным GIL нужно использовать новую функцию Py_NewInterpreterFromConfig() :
PyInterpreterConfig config = .check_multi_interp_extensions = 1, .gil = PyInterpreterConfig_OWN_GIL, >; PyThreadState *tstate = NULL; PyStatus status = Py_NewInterpreterFromConfig(&tstate, &config); if (PyStatus_Exception(status)) return -1; > /* Новый интерпретатор теперь активен в текущем потоке. */
- КРАТКИЙ ОБЗОР МАТЕРИАЛА.
- Global Interpreter Lock (GIL)
Разбираемся в Python GIL
В этой статье мы разберем , что такое GIL. Для чего его реализовали в Python и какие проблемы он решает и какие проблемы он создает и почему его не убирают из Python. Нужно отметить тот факт, что GIL используется в наиболее распространенной реализации интепретатора Python , а именно в CPython.
GIL (Global Interpreter Lock – глобальная блокировка интерпретатора) — это булево значение в интерпритаторе Python(CPython), защищенное мьютексом.Это своеобразная блокировка , которая позволяет только одному потоку управлять интерпретатором Python. Получается, что в любой момент времени выполняется только один поток. И тем самым в Python не могут выполняться параллельно несколько потоков из-за это ограничения.
Причины , по которым в Python используется GIL
Так почему же используется это блокировка , которая не позволяет нескольким потокам выполняться параллельно ? Ведь таким образом мы не можем писать на Python(CPython) высокопроизводительные многопоточные программы.
Чтобы ответить на этот вопрос нам нужно понять как работает сборщик мусора в CPython
В отличие от С и других языков программирования Python управляет объектами с помощью подсчета ссылок. Диспетчер в памяти отслеживает количество ссылок на каждый объект в программе и когда количество ссылок на данный объект достигает нуля , то сборщик мусора удаляет этот объект, тем самым освобождая память. Это все делается самим интерпретатором Python , освобождая программиста от всех хлопот связанных с управления памятью.
Счетчик ссылок увеличивается , если объекту присваивается новое имя или этот объект помещается в словарь или в кортеж , но также , счетчик ссылок уменьшается, когда ссылка на объект переназначается, когда ссылка на объект выходит из области видимости. И как мы отмечали выше , когда счетчик ссылок для объекта становится равным нулю, то сборщик мусора освобождает память выделенный под этот объект.
Все это прекрасно работает , если программа на Python работает в однопоточном режиме. А вот когда программа запускается в многопоточном режиме и если не используется GIL , то возникают проблемы с преждевременным удалением объектов и другими проблемами связанными с управлением памяти. Это происходит из-за того, что несколько потоков одновременно могут увеличивать или уменьшать значения счётчика ссылок на объект.
Поэтому для решения этих проблем в Python(CPython) был добавлен GIL. GIL легко было реализовать и интегрировать в Python. Реализация GIL увеличил производительность однопоточных приложений, поскольку управление велось только одним блокиратором. И еще это архитектурное решение на заре развития Python привел к тому , что очень многие библиотеки написанные на Си были интегрированны для Python , которые повлияли на его популярность. Ведь разработчикам , которым важна была скорость разработки , важно было наличие готовых библиотек, а не скорость работы программ в многопоточных приложениях.
Но времена меняются и технический прогресс не стоит на месте и то архитектурное решение , которое вначале сделало Python таким популярным, стал объектом других проблем, связанных с реализацией многопточных приложений
Что такое глобальная блокировка интерпретатора Python (GIL)?
Python Global Interpreter Lock (глобальная блокировка интерпретатора), или GIL, простыми словами, представляет собой мьютекс (или блокировку), который позволяет только одному потоку контролировать интерпретатор Python.
Это означает, что в любой момент времени в состоянии выполнения может находиться только один поток. Влияние GIL не заметно для разработчиков, выполняющих однопоточные программы, но оно может стать узким местом в производительности процессорного и многопоточного кода.
Поскольку GIL позволяет одновременно выполнять только один поток даже в многопоточной архитектуре с более чем одним ядром процессора, он приобрел репутацию «печально известной» особенности Python.
Из этой статьи вы узнаете, как глобальная блокировка интерпретатора влияет на производительность ваших Python-программ и как можно уменьшить ее влияние на ваш код.
Какую проблему решил GIL для Python?
В Python подсчет ссылок используется для управления памятью. Это означает, что объекты, созданные в Python, имеют переменную reference count , которая отслеживает количество ссылок, указывающих на объект. Когда этот счетчик достигает нуля, память, занимаемая объектом, освобождается.
Для демонстрации работы подсчета ссылок рассмотрим небольшой пример кода:
>>> import sys >>> a = [] >>> b = a >>> sys.getrefcount(a) 3
В приведенном примере количество ссылок на пустой объект-список [] составило 3. На объект-список ссылались a , b и аргумент, переданный в sys.getrefcount() .
Проблема заключалась в том, что переменная подсчета ссылок нуждалась в защите от условий гонки, когда два потока одновременно увеличивают или уменьшают ее значение. Если такое происходит, это может привести либо к утечке памяти, которая никогда не освобождается, либо, что еще хуже, к некорректному освобождению памяти, когда ссылка на объект все еще существует. Это может привести к сбоям или другим «странным» ошибкам в программах на Python.
Переменная количества ссылок может быть сохранена путем добавления блокировок ко всем структурам данных, разделяемым между потоками, чтобы исключить их непоследовательное изменение.
Но добавление блокировки к каждому объекту или группе объектов означает наличие нескольких блокировок. Это может привести к другой проблеме — взаимным блокировкам или дедлокам. Другим побочным эффектом может стать снижение производительности, вызванное многократным получением и освобождением блокировок.
GIL — это единственная блокировка самого интерпретатора, которая добавляет правило, что выполнение любого байткода Python требует получения блокировки интерпретатора. Это предотвращает дедлоки (поскольку существует только одна блокировка) и не приводит к большим накладным расходам на производительность. Однако это фактически делает любую Python-программу, привязанную к процессору, однопоточной.
GIL, хотя и используется в интерпретаторах других языков, например Ruby, не является единственным решением этой проблемы. Некоторые языки обходят требование GIL для потокобезопасного управления памятью, используя подходы, отличные от подсчета ссылок, например, сборку мусора.
С другой стороны, это означает, что такие языки часто вынуждены компенсировать потерю преимуществ GIL в производительности однопоточных систем за счет добавления других функций, повышающих производительность, например JIT-компиляторов.
Почему в качестве решения был выбран GIL?
Почему же в Python был использован подход, который, казалось бы, так мешает? Было ли это неудачным решением разработчиков Python?
По мнению Ларри Хастингса, дизайнерское решение GIL — это одна из тех вещей, которые сделали Python таким популярным, каким он является сегодня.
Язык Python появился еще в те времена, когда в операционных системах не было понятия потоков. Python был создан простым в использовании, чтобы сделать разработку более быстрой. В результате его стали применять многие разработчики.
Для существующих библиотек на языке C было написано множество расширений, которые были необходимы в Python. Для предотвращения непоследовательных изменений эти расширения на C требовали потокобезопасного управления памятью, что и обеспечивалось GIL.
GIL прост в реализации и легко добавляется в Python. Он обеспечивает прирост производительности однопоточных программ, поскольку необходимо управлять только одной блокировкой.
Библиотеки языка C, которые не были потокобезопасными, стали легче интегрироваться. И эти расширения языка C стали одной из причин, по которой Python был легко принят различными сообществами.
Как видите, GIL был прагматичным решением сложной проблемы, с которой столкнулись CPython-разработчики на ранних этапах существования Python.
Влияние на многопоточные программы Python
Если посмотреть на типичную программу на языке Python — да и на любую другую компьютерную программу, — то можно заметить разницу между теми, которые по своей производительности привязаны к процессору, и теми, которые привязаны к вводу-выводу.
Программы, привязанные к процессору (CPU-bound) — это программы, которые доводят процессор до предела. К ним относятся программы, выполняющие математические вычисления, такие как умножение матриц, поиск, обработка изображений и т.д.
Программы, связанные со вводом/выводом (I/O-bound) — это программы, которые тратят время на ожидание ввода/вывода, который может поступать от пользователя, файла, базы данных, сети и т.д. Иногда программам, связанным со вводом/выводом, приходится ждать значительное время, пока они получат то, что им нужно от источника, поскольку источнику может потребоваться выполнить свою собственную обработку, прежде чем вход/выход будет готов. Например, пользователь может задуматься, что именно ввести в строку ввода, или запрос к базе данных может выполняться в своем собственном процессе.
Рассмотрим простую привязанную к процессору программу, выполняющую обратный отсчет времени:
# single_threaded.py import time from threading import Thread COUNT = 50000000 def countdown(n): while n>0: n -= 1 start = time.time() countdown(COUNT) end = time.time() print('Time taken in seconds -', end - start)
Запуск этого кода на моей системе с 4 ядрами дал следующий результат:
$ python single_threaded.py Time taken in seconds - 6.20024037361145
Теперь я немного модифицировал код, чтобы сделать тот же обратный отсчет с помощью двух параллельных потоков:
# multi_threaded.py import time from threading import Thread COUNT = 50000000 def countdown(n): while n>0: n -= 1 t1 = Thread(target=countdown, args=(COUNT//2,)) t2 = Thread(target=countdown, args=(COUNT//2,)) start = time.time() t1.start() t2.start() t1.join() t2.join() end = time.time() print('Time taken in seconds -', end - start)
И когда я запустил его снова, то результат был таким:
$ python multi_threaded.py Time taken in seconds - 6.924342632293701
Как видно, обе версии завершаются практически за одинаковое время. В многопоточном варианте GIL не позволил параллельно выполнять потоки, привязанные к процессору.
GIL не оказывает существенного влияния на производительность многопоточных программ, связанных со вводом/выводом, так как блокировка разделяется между потоками, пока они ожидают ввода/вывода.
Но программа, потоки которой полностью привязаны к процессору, например, программа, обрабатывающая изображение по частям с помощью потоков, не только станет однопоточной из-за блокировки, но и получит увеличение времени выполнения по сравнению со сценарием, когда она была написана полностью однопоточной.
Это увеличение является результатом накладных расходов на установку и освобождение блокировки.
Почему до сих пор не убрали GIL?
Разработчики Python получают много жалоб по этому поводу. Но такой популярный язык, как Python, не может внести столь существенное изменение, как удаление GIL, не вызвав проблем с обратной совместимостью.
Очевидно, что глобальную блокировку интерпретатора можно удалить. Это неоднократно делалось в прошлом разработчиками и исследователями. Но все эти попытки приводили к поломке существующих расширений на языке C, которые в значительной степени зависели от решений, предоставляемых GIL.
Конечно, существуют и другие решения проблемы, которую решает GIL, но некоторые из них снижают производительность однопоточных и многопоточных программ, связанных со вводом-выводом, а некоторые просто слишком сложны. В конце концов, вы же не хотите, чтобы ваши существующие программы на Python работали медленнее после выхода новой версии?
Создатель и BDFL Python Гвидо ван Россум дал ответ сообществу в сентябре 2007 года в своей статье «Нелегко удалить GIL»:
«Я бы приветствовал набор патчей в Py3k только если производительность для однопоточной программы (и для многопоточной, но связанной со вводом/выводом) не уменьшится«.
И это условие не было выполнено ни в одной из предпринятых с тех пор попыток.
Почему GIL не убрали в Python 3?
Python 3 действительно имел шанс начать множество функций с нуля, и в этом процессе были нарушены некоторые существующие C-расширения, которые затем потребовали изменений и портирования, чтобы работать с Python 3. Именно поэтому ранние версии Python 3 так медленно принимались сообществом.
Но почему заодно не был удален и GIL?
Удаление GIL могло бы сделать Python 3 медленнее по сравнению с Python 2 в однопоточной производительности, и можно представить, какие это могло бы повлечь последствия. Вследствие этого Python 3 по-прежнему его использует.
Однако в Python 3 было внесено существенное усовершенствование в существующий механизм глобальной блокировки интерпретатора.
Мы обсудили влияние GIL на многопоточные программы с CPU-bound нагрузкой и I/O-bound программы, но как быть с программами, в которых часть потоков привязана к вводу-выводу, а часть — к процессору?
Известно, что в таких программах GIL Python приводил к «голоданию» потоков, связанных со вводом-выводом, не давая им возможности получить GIL от потоков, связанных с процессором.
Это происходило из-за механизма, встроенного в Python, который обязывал потоки освобождать GIL после фиксированного интервала непрерывного использования, и если никто другой не захватывал GIL, то тот же самый поток мог продолжить его использование.
>>> import sys >>> # The interval is set to 100 instructions: >>> sys.getcheckinterval() 100
Проблема этого механизма заключалась в том, что большую часть времени поток, связанный с центральным процессором, сам снова захватывал GIL, прежде чем другие потоки смогли бы его получить. Это исследовалось Дэвидом Бизли, и визуализации можно найти здесь.
Эта проблема была исправлена в Python 3.2 в 2009 году Антуаном Питру, который добавил механизм, позволяющий отслеживать количество отклоненных запросов на получение GIL другими потоками и не позволяющий текущему потоку снова получить GIL, пока другим потокам не дадут шанс выполниться.
Как работать с GIL в Python
Механизм глобальной блокировки интерпретатора вызывает у вас проблемы, то можно попробовать несколько подходов.
Многозадачность через многопроцессорность против многозадачности через многозадачность
Самый популярный способ — это использовать подход многозадачности через многопроцессорность, где вы используете несколько процессов вместо потоков.
Каждый процесс Python получает свой собственный интерпретатор Python и область памяти, поэтому проблемы с GIL не возникают.
В Python есть модуль multiprocessing, который позволяет легко создавать процессы, как в следующем примере:
from multiprocessing import Pool import time COUNT = 50000000 def countdown(n): while n>0: n -= 1 if __name__ == '__main__': pool = Pool(processes=2) start = time.time() r1 = pool.apply_async(countdown, [COUNT//2]) r2 = pool.apply_async(countdown, [COUNT//2]) pool.close() pool.join() end = time.time() print('Time taken in seconds -', end - start)
Запуск этой программы на моей системе дал следующий результат:
$ python multiprocess.py Time taken in seconds - 4.060242414474487
Приличный прирост производительности по сравнению с многопоточной версией, не так ли?
Время не уменьшилось и вполовину от того, что мы видели выше, поскольку управление процессами имеет свои собственные накладные расходы. Несколько процессов тяжелее, чем несколько потоков, поэтому следует иметь в виду, что это может стать узким местом при масштабировании.
Альтернативные интерпретаторы Python
Python имеет несколько реализаций интерпретаторов. Наиболее популярными являются CPython, Jython, IronPython и PyPy, написанные на C, Java, C# и Python соответственно. GIL существует только в оригинальной реализации Python, которой является CPython. Если ваша программа с ее библиотеками доступна для одной из других реализаций, то вы можете попробовать и их.
Просто подождите
Многие пользователи Python используют преимущества GIL для однопоточной работы. Но многопоточным программистам не стоит беспокоиться, так как некоторые из самых светлых умов в сообществе Python работают над удалением GIL из CPython. Одна из таких попыток известна как Gilectomy.
Механизм глобальной блокировки интерпретатора в Python часто считается загадочной и сложной темой. Но имейте в виду, что вас, как разработчика на языке Python, он обычно затрагивает только в том случае, если вы пишете расширения для языка C или используете в своих программах многопоточность, привязанную к процессору.
В таком случае эта статья должна дать вам все необходимое для понимания того, что такое GIL и как с ним работать в своих проектах. Если же вы хотите разобраться в низкоуровневой работе GIL, то я рекомендую вам посмотреть доклад Дэвида Бизли «Понимание Python GIL» .
What Is the Python Global Interpreter Lock (GIL)?
The Python Global Interpreter Lock or GIL, in simple words, is a mutex (or a lock) that allows only one thread to hold the control of the Python interpreter.
This means that only one thread can be in a state of execution at any point in time. The impact of the GIL isn’t visible to developers who execute single-threaded programs, but it can be a performance bottleneck in CPU-bound and multi-threaded code.
Since the GIL allows only one thread to execute at a time even in a multi-threaded architecture with more than one CPU core, the GIL has gained a reputation as an “infamous” feature of Python.
In this article you’ll learn how the GIL affects the performance of your Python programs, and how you can mitigate the impact it might have on your code.
What Problem Did the GIL Solve for Python?
Python uses reference counting for memory management. It means that objects created in Python have a reference count variable that keeps track of the number of references that point to the object. When this count reaches zero, the memory occupied by the object is released.
Let’s take a look at a brief code example to demonstrate how reference counting works:
>>> import sys >>> a = [] >>> b = a >>> sys.getrefcount(a) 3
In the above example, the reference count for the empty list object [] was 3. The list object was referenced by a , b and the argument passed to sys.getrefcount() .
Back to the GIL:
The problem was that this reference count variable needed protection from race conditions where two threads increase or decrease its value simultaneously. If this happens, it can cause either leaked memory that is never released or, even worse, incorrectly release the memory while a reference to that object still exists. This can cause crashes or other “weird” bugs in your Python programs.
This reference count variable can be kept safe by adding locks to all data structures that are shared across threads so that they are not modified inconsistently.
But adding a lock to each object or groups of objects means multiple locks will exist which can cause another problem—Deadlocks (deadlocks can only happen if there is more than one lock). Another side effect would be decreased performance caused by the repeated acquisition and release of locks.
The GIL is a single lock on the interpreter itself which adds a rule that execution of any Python bytecode requires acquiring the interpreter lock. This prevents deadlocks (as there is only one lock) and doesn’t introduce much performance overhead. But it effectively makes any CPU-bound Python program single-threaded.
The GIL, although used by interpreters for other languages like Ruby, is not the only solution to this problem. Some languages avoid the requirement of a GIL for thread-safe memory management by using approaches other than reference counting, such as garbage collection.
On the other hand, this means that those languages often have to compensate for the loss of single threaded performance benefits of a GIL by adding other performance boosting features like JIT compilers.
Why Was the GIL Chosen as the Solution?
So, why was an approach that is seemingly so obstructing used in Python? Was it a bad decision by the developers of Python?
Well, in the words of Larry Hastings, the design decision of the GIL is one of the things that made Python as popular as it is today.
Python has been around since the days when operating systems did not have a concept of threads. Python was designed to be easy-to-use in order to make development quicker and more and more developers started using it.
A lot of extensions were being written for the existing C libraries whose features were needed in Python. To prevent inconsistent changes, these C extensions required a thread-safe memory management which the GIL provided.
The GIL is simple to implement and was easily added to Python. It provides a performance increase to single-threaded programs as only one lock needs to be managed.
C libraries that were not thread-safe became easier to integrate. And these C extensions became one of the reasons why Python was readily adopted by different communities.
As you can see, the GIL was a pragmatic solution to a difficult problem that the CPython developers faced early on in Python’s life.
The Impact on Multi-Threaded Python Programs
When you look at a typical Python program—or any computer program for that matter—there’s a difference between those that are CPU-bound in their performance and those that are I/O-bound.
CPU-bound programs are those that are pushing the CPU to its limit. This includes programs that do mathematical computations like matrix multiplications, searching, image processing, etc.
I/O-bound programs are the ones that spend time waiting for Input/Output which can come from a user, file, database, network, etc. I/O-bound programs sometimes have to wait for a significant amount of time till they get what they need from the source due to the fact that the source may need to do its own processing before the input/output is ready, for example, a user thinking about what to enter into an input prompt or a database query running in its own process.
Let’s have a look at a simple CPU-bound program that performs a countdown:
# single_threaded.py import time from threading import Thread COUNT = 50000000 def countdown(n): while n>0: n -= 1 start = time.time() countdown(COUNT) end = time.time() print('Time taken in seconds -', end - start)
Running this code on my system with 4 cores gave the following output:
$ python single_threaded.py Time taken in seconds - 6.20024037361145
Now I modified the code a bit to do to the same countdown using two threads in parallel:
# multi_threaded.py import time from threading import Thread COUNT = 50000000 def countdown(n): while n>0: n -= 1 t1 = Thread(target=countdown, args=(COUNT//2,)) t2 = Thread(target=countdown, args=(COUNT//2,)) start = time.time() t1.start() t2.start() t1.join() t2.join() end = time.time() print('Time taken in seconds -', end - start)
And when I ran it again:
$ python multi_threaded.py Time taken in seconds - 6.924342632293701
As you can see, both versions take almost same amount of time to finish. In the multi-threaded version the GIL prevented the CPU-bound threads from executing in parallel.
The GIL does not have much impact on the performance of I/O-bound multi-threaded programs as the lock is shared between threads while they are waiting for I/O.
But a program whose threads are entirely CPU-bound, e.g., a program that processes an image in parts using threads, would not only become single threaded due to the lock but will also see an increase in execution time, as seen in the above example, in comparison to a scenario where it was written to be entirely single-threaded.
This increase is the result of acquire and release overheads added by the lock.
Why Hasn’t the GIL Been Removed Yet?
The developers of Python receive a lot of complaints regarding this but a language as popular as Python cannot bring a change as significant as the removal of GIL without causing backward incompatibility issues.
The GIL can obviously be removed and this has been done multiple times in the past by the developers and researchers but all those attempts broke the existing C extensions which depend heavily on the solution that the GIL provides.
Of course, there are other solutions to the problem that the GIL solves but some of them decrease the performance of single-threaded and multi-threaded I/O-bound programs and some of them are just too difficult. After all, you wouldn’t want your existing Python programs to run slower after a new version comes out, right?
The creator and BDFL of Python, Guido van Rossum, gave an answer to the community in September 2007 in his article “It isn’t Easy to remove the GIL”:
“I’d welcome a set of patches into Py3k only if the performance for a single-threaded program (and for a multi-threaded but I/O-bound program) does not decrease”
And this condition hasn’t been fulfilled by any of the attempts made since.
Why Wasn’t It Removed in Python 3?
Python 3 did have a chance to start a lot of features from scratch and in the process, broke some of the existing C extensions which then required changes to be updated and ported to work with Python 3. This was the reason why the early versions of Python 3 saw slower adoption by the community.
But why wasn’t GIL removed alongside?
Removing the GIL would have made Python 3 slower in comparison to Python 2 in single-threaded performance and you can imagine what that would have resulted in. You can’t argue with the single-threaded performance benefits of the GIL. So the result is that Python 3 still has the GIL.
But Python 3 did bring a major improvement to the existing GIL—
We discussed the impact of GIL on “only CPU-bound” and “only I/O-bound” multi-threaded programs but what about the programs where some threads are I/O-bound and some are CPU-bound?
In such programs, Python’s GIL was known to starve the I/O-bound threads by not giving them a chance to acquire the GIL from CPU-bound threads.
This was because of a mechanism built into Python that forced threads to release the GIL after a fixed interval of continuous use and if nobody else acquired the GIL, the same thread could continue its use.
>>> import sys >>> # The interval is set to 100 instructions: >>> sys.getcheckinterval() 100
The problem in this mechanism was that most of the time the CPU-bound thread would reacquire the GIL itself before other threads could acquire it. This was researched by David Beazley and visualizations can be found here.
This problem was fixed in Python 3.2 in 2009 by Antoine Pitrou who added a mechanism of looking at the number of GIL acquisition requests by other threads that got dropped and not allowing the current thread to reacquire GIL before other threads got a chance to run.
How to Deal With Python’s GIL
If the GIL is causing you problems, here a few approaches you can try:
Multi-processing vs multi-threading: The most popular way is to use a multi-processing approach where you use multiple processes instead of threads. Each Python process gets its own Python interpreter and memory space so the GIL won’t be a problem. Python has a multiprocessing module which lets us create processes easily like this:
from multiprocessing import Pool import time COUNT = 50000000 def countdown(n): while n>0: n -= 1 if __name__ == '__main__': pool = Pool(processes=2) start = time.time() r1 = pool.apply_async(countdown, [COUNT//2]) r2 = pool.apply_async(countdown, [COUNT//2]) pool.close() pool.join() end = time.time() print('Time taken in seconds -', end - start)
Running this on my system gave this output:
$ python multiprocess.py Time taken in seconds - 4.060242414474487
A decent performance increase compared to the multi-threaded version, right?
The time didn’t drop to half of what we saw above because process management has its own overheads. Multiple processes are heavier than multiple threads, so, keep in mind that this could become a scaling bottleneck.
Alternative Python interpreters: Python has multiple interpreter implementations. CPython, Jython, IronPython and PyPy, written in C, Java, C# and Python respectively, are the most popular ones. GIL exists only in the original Python implementation that is CPython. If your program, with its libraries, is available for one of the other implementations then you can try them out as well.
Just wait it out: While many Python users take advantage of the single-threaded performance benefits of GIL. The multi-threading programmers don’t have to fret as some of the brightest minds in the Python community are working to remove the GIL from CPython. One such attempt is known as the Gilectomy.
The Python GIL is often regarded as a mysterious and difficult topic. But keep in mind that as a Pythonista you’re usually only affected by it if you are writing C extensions or if you’re using CPU-bound multi-threading in your programs.
In that case, this article should give you everything you need to understand what the GIL is and how to deal with it in your own projects. And if you want to understand the low-level inner workings of GIL, I’d recommend you watch the Understanding the Python GIL talk by David Beazley.
Mark as Completed
Get a short & sweet Python Trick delivered to your inbox every couple of days. No spam ever. Unsubscribe any time. Curated by the Real Python team.

About Abhinav Ajitsaria
Abhinav is a Software Engineer from India. He loves to talk about system design, machine learning, AWS and of course, Python.
Each tutorial at Real Python is created by a team of developers so that it meets our high quality standards. The team members who worked on this tutorial are:


Master Real-World Python Skills With Unlimited Access to Real Python
Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas:
Master Real-World Python Skills
With Unlimited Access to Real Python
Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas:
What Do You Think?
Rate this article:
What’s your #1 takeaway or favorite thing you learned? How are you going to put your newfound skills to use? Leave a comment below and let us know.
Commenting Tips: The most useful comments are those written with the goal of learning from or helping out other students. Get tips for asking good questions and get answers to common questions in our support portal. Looking for a real-time conversation? Visit the Real Python Community Chat or join the next “Office Hours” Live Q&A Session. Happy Pythoning!
Related Tutorial Categories: advanced python
