Python Language
Работа над глобальным блокировщиком перевода (GIL)
Поиск…
замечания
Почему существует GIL?
GIL работает в CPython с момента создания потоков Python в 1992 году. Он разработан для обеспечения безопасности потоков при работе с кодом Python. Устные переводчики Python, написанные с помощью GIL, предотвращают одновременное выполнение нескольких собственных потоков от байт-кодов Python. Это облегчает для плагинов обеспечение того, чтобы их код был потокобезопасным: просто заблокируйте GIL, и только ваш активный поток может работать, поэтому ваш код автоматически потокобезопасен.Краткая версия: GIL гарантирует, что независимо от того, сколько процессоров и потоков у вас есть, только один поток интерпретатора python будет запускаться за один раз.
Это имеет много преимуществ в удобстве использования, но также имеет множество негативных преимуществ.
Обратите внимание, что GIL не является требованием языка Python. Следовательно, вы не можете получить доступ к GIL напрямую из стандартного кода python. Не все реализации Python используют GIL.
Интерпретаторы, которые имеют GIL: CPython, PyPy, Cython (но вы можете отключить GIL с nogil
)
Переводчики, не имеющие GIL: Jython, IronPython
Подробная информация о том, как работает GIL:
Когда поток работает, он блокирует GIL. Когда поток хочет запустить, он запрашивает GIL и ждет, пока он не будет доступен. В CPython, перед версией 3.2, текущий поток проверял бы после некоторого количества инструкций python, чтобы узнать, нужен ли другой код блокировке (то есть, он освободил блокировку, а затем запросил ее снова). Этот метод, как правило, вызывал головокружение потоков, главным образом потому, что поток, который освободил блокировку, приобрел его снова, прежде чем ожидающие потоки имели возможность проснуться. Начиная с 3.2, потоки, которые хотят, чтобы GIL ожидал блокировки в течение некоторого времени, и после этого времени, они устанавливают общую переменную, которая заставляет текущую нить давать. Однако это может привести к значительному увеличению времени выполнения. См. Ссылки ниже от dabeaz.com (в разделе ссылок) для более подробной информации.CPython автоматически освобождает GIL, когда поток выполняет операцию ввода-вывода. Библиотеки обработки изображений и операции хруста с номером номера освобождают GIL перед выполнением их обработки.
Преимущества GIL
Для переводчиков, которые используют GIL, GIL является системным. Он используется для сохранения состояния приложения. Преимущества включают:- Сбор мусора - подсчет ссылок на потоки должен быть изменен, когда GIL заблокирован. В CPython вся коллекция garbarge привязана к GIL. Это большой; см. статью wiki python.org о GIL (см. ниже в ссылках) для получения подробной информации о том, что должно быть функционально, если вы хотите удалить GIL.
- Простота для программистов, занимающихся GIL - блокировка, все упрощена, но легко кодируется
- Упрощает импорт модулей с других языков
Последствия GIL
GIL только позволяет одному потоку запускать код python за раз в интерпретаторе python. Это означает, что многопоточность процессов, выполняющих строгий код python, просто не работает. При использовании потоков против GIL у вас, вероятно, будет хуже производительность с потоками, чем при запуске в одном потоке.Рекомендации:
https://wiki.python.org/moin/GlobalInterpreterLock - краткое изложение того, что он делает, мелкие детали всех преимуществ
http://programmers.stackexchange.com/questions/186889/why-was-python-written-with-the-gil - четко написанное резюме
http://www.dabeaz.com/python/UnderstandingGIL.pdf - как работает GIL и почему он замедляется на нескольких ядрах
http://www.dabeaz.com/GIL/gilvis/index.html - визуализация данных, показывающих, как GIL блокирует потоки
http://jeffknupp.com/blog/2012/03/31/pythons-hardest-problem/ - простая для понимания история проблемы GIL
https://jeffknupp.com/blog/2013/06/30/pythons-hardest-problem-revisited/ - подробная информация о способах работы с ограничениями GIL
Multiprocessing.Pool
Простой ответ, когда вы спрашиваете, как использовать потоки в Python, это: «Не используйте. Вместо этого используйте процессы». Многопроцессорный модуль позволяет создавать процессы с похожим синтаксисом для создания потоков, но я предпочитаю использовать их удобный объект Pool.
Используя код, который Дэвид Бэйзли впервые использовал для выявления опасностей потоков против GIL , мы перепишем его с помощью многопроцессорной обработки. Пул :
Код Дэвида Бэзли, который показал проблемы с резьбой GIL
from threading import Thread
import time
def countdown(n):
while n > 0:
n -= 1
COUNT = 10000000
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 end-start
Переписано с использованием многопроцессорности.Pool: import multiprocessing
import time
def countdown(n):
while n > 0:
n -= 1
COUNT = 10000000
start = time.time()
with multiprocessing.Pool as pool:
pool.map(countdown, [COUNT/2, COUNT/2])
pool.close()
pool.join()
end = time.time()
print(end-start)
Вместо создания потоков это создает новые процессы. Поскольку каждый процесс является его собственным интерпретатором, не происходит столкновений GIL. multiprocessing.Pool откроет столько процессов, сколько на нем будет ядер, хотя в приведенном выше примере ему потребуется только два. В реальном сценарии вы хотите создать свой список, чтобы иметь как минимум столько же, сколько на вашем компьютере. Пул будет запускать функцию, которую вы укажете ей для запуска с каждым аргументом, вплоть до количества процессов, которые он создает. Когда функция завершится, все остальные функции в списке будут запущены в этом процессе.
Я обнаружил, что даже используя оператор with
, если вы не закрываете и не присоединяетесь к пулу, процессы продолжают существовать. Чтобы очистить ресурсы, я всегда закрываю и присоединяюсь к моим пулам.
Cython nogil:
Cython - альтернативный интерпретатор python. Он использует GIL, но позволяет отключить его. См. Их документацию
В качестве примера, используя код, который Дэвид Бэйсли впервые использовал, чтобы показать опасности потоков против GIL , мы перепишем его с помощью nogil:
Код Дэвида Бэзли, который показал проблемы с резьбой GIL
from threading import Thread
import time
def countdown(n):
while n > 0:
n -= 1
COUNT = 10000000
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 end-start
Переписано с использованием nogil (ТОЛЬКО РАБОТАЕТ В ЦИТОНЕ):
from threading import Thread
import time
def countdown(n):
while n > 0:
n -= 1
COUNT = 10000000
with nogil:
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 end-start
Это так просто, если вы используете cython. Обратите внимание, что в документации указано, что вы не должны изменять какие-либо объекты python:
Код в теле оператора не должен каким-либо образом манипулировать объектами Python и не должен называть ничего, что манипулирует объектами Python, без предварительного повторного приобретения GIL. Cython в настоящее время не проверяет это.