обо мне
Небольшая программная обезьяна в мире программирования, в настоящее время работает руководителем команды в предпринимательской команде. Стек технологий включает Android, Python, Java и Go, который также является основным стеком технологий нашей команды. Контакт: hylinux1024@gmail.com
0x00 разрешит, что такое глобальный замок (GIL)
A global interpreter lock (GIL) is a mechanism used in computer-language interpreters to synchronize the execution of threads so that only one native thread can execute at a time. --Цитата из википедии
Из вышеприведенного определения видно, чтоGILСинтаксический анализатор языка программирования, используемый для синхронизации выполнения потокаСинхронизированный запорный механизм. Многие языки программирования имеютGIL,НапримерPython,Ruby.
0x01 Зачем нужен GIL
PythonПоскольку это объектно-ориентированный язык программирования с динамической типизацией, код, написанный разработчиком, анализируется и последовательно выполняется синтаксическим анализатором.
большинство людей в настоящее время используютPythonПарсерCPythonпри условии, покаCPythonПарсерИспользуйте подсчет ссылок для управления памятью, чтобы обеспечить безопасность многопоточности, см.global intepreter lock, только получитьGILпоток для выполнения. Без этой блокировки даже простые операции в многопоточном кодировании могут вызвать проблемы с общими переменными, изменяемыми несколькими потоками одновременно. Например, есть два потокаКогда ссылка делается на один и тот же объект одновременно, оба потока будут увеличивать счетчик ссылок переменной с 0 до 1., что явно неверно.
в состоянии пройтиsysМодуль получает счетчик ссылок переменной
>>> import sys
>>> a = []
>>> sys.getrefcount(a)
2
>>> b = a
>>> sys.getrefcount(a)
3
sys.getrefcount()Ссылка на параметр в методе также приводит к увеличению счетчика.
Можно ли использовать отдельную блокировку для каждой переменной для синхронизации?
Если есть несколько блокировок, это легко происходит при синхронизации потоков.тупик, да и сложность программирования тоже увеличится. Когда в мире есть только одна блокировка, все потоки конкурируют за блокировку, поэтому нет ситуации ожидания блокировок друг друга, и реализация кодирования упрощается. Кроме того, влияние на один поток при наличии только одной блокировки не очень велико.
0x02 Можно ли удалить GIL?
Pythonосновная команда разработчиков иPythonРеакция технологов сообщества на удалениеGILБыло предпринято много попыток, но в итоге нет решения, которое удовлетворило бы все стороны.
В дополнение к методам управления памятьюподсчет ссылокКроме того, некоторые языки программирования используют управление памятью, чтобы избежать ссылок на глобальные блокировки разрешения.вывоз мусорамеханизм.
Конечно, это также означает, что те языки, которые используют сборку мусора, должны улучшить производительность в других областях (таких какJITкомпиляции), чтобы компенсировать потерю производительности выполнения однопоточных программ.
заPython, выберитеподсчет ссылоккак управление памятью. С одной стороны, это гарантируетПроизводительность однопоточного выполнения программы,с другой стороныGILУпрощает реализацию кода.
существуетPythonМногие функции вCбиблиотека для реализации, в то время как вCЧтобы обеспечить безопасность потоков в библиотеке, это также зависит отGIL.
Итак, когда кто-то успешно удалилGILпосле,PythonПрограмма не становится быстрее, потому что большинство людей используют однопоточный сценарий.
0x03 Влияние на многопоточные программы
приходи первымGILправильноIOинтенсивная программа иCPUРазница между интенсивными программами.
Операции, такие как файл чтения и записи, сетевые запросы, доступ к базе данных и т. Д.IOИнтеграл, их особенностинужно подождатьIOвремя работы, а затем перейдите к следующему шагу; такие операции, как математические вычисления, обработка изображений и матричные операцииCPUинтенсивные, для них характернынужно многоCPUвычислительная мощность для поддержки.
заIOИнтенсивная работа, поток, который в данный момент владеет блокировкой, сначала освободит блокировку, а затем выполнитIOоперацию и, наконец, получить блокировку. Когда поток снимает блокировку, он сохраняет текущее состояние потока в глобальной переменной.PThreadStateВ структуре данных, когда поток получает блокировку, восстанавливается предыдущее состояние потока.
Опишите процесс выполнения словами
保存当前线程的状态到一个全局变量中
释放GIL
... 执行IO操作 ...
获取GIL
从全局变量中恢复之前的线程状态
В следующем коде указано время выполнения тестового одного потока 5 миллионов раз.
import time
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)
# 执行结果
# Time taken in seconds - 2.44541597366333
На моем 8 ядреmacbookДля запуска требуется около 2,4 секунды, затем посмотрите на многопоточную версию.
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)
# 执行结果
# Time taken in seconds - 2.4634649753570557
Приведенный выше код выполняется 2,5 миллиона раз на поток. Если потоки параллельны, время выполнения должно быть примерно в два раза меньше, чем у однопоточной версии выше. Однако время выполнения на моем компьютере составляет около 2,5 секунд!
Вместо того, чтобы быть более эффективным, многопоточность требует больше времени. Этот пример показываетPythonПотоки выполняются последовательно, и только поток, который получает блокировку, может получить время выполнения синтаксического анализатора. Дополнительное время для многопоточного выполнения — это время, затрачиваемое на получение и снятие блокировок.
Так как же добиться высокой параллелизма?
Ответ заключается в использовании многопроцессорности.В предыдущей статье было представлено использование многопроцессорного
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)
# 执行结果
# Time taken in seconds - 1.2389559745788574
При многопроцессорной обработке каждый процесс выполняется 2,5 миллиона раз, что занимает около 1,2 секунды. Примерно в два раза меньше, чем в версии с резьбой выше.
Конечно другоеPythonпарсер, напримерJython,IronPythonилиPyPy.
Поскольку каждый поток должен получить блокировку перед выполнением, что мне делать, если поток получает блокировку и не освобождает ее, пока она не будет занята?
IOИнтенсивные программы будут активно снимать блокировки, но дляCPUинтенсивная программа илиIOинтенсивный иCPUСмешанные программы, как будет работать парсер?
Ранняя практика былаPythonЗаставит поток освободиться после 100 инструкцийGILДайте другим потокам возможность выполниться.
Эту конфигурацию можно получить,
>>> import sys
>>> sys.getcheckinterval()
100
Также напечатал приведенное ниже предупреждение о выводе на моем компьютере.
Warning (from warnings module):
File "__main__", line 1
DeprecationWarning: sys.getcheckinterval() and sys.setcheckinterval() are deprecated. Use sys.getswitchinterval() instead.
смыслsys.getcheckinterval()метод устарел и должен использоватьсяsys.getswitchinterval()метод.
Поскольку традиционная реализация принуждения потока снимать блокировку каждый раз, когда анализируется 100 инструкций, приведет кCPUИнтенсивные темы всегда будут заниматьGILиIOИнтенсивная многопоточность никогда не решит проблему.Таким образом, появилась новая схема переключения потоков.
>>> sys.getswitchinterval()
0.005
Этот метод возвращает 0,05 секунды, что означает, что каждый поток освобождается через 0,05 секунды выполнения.GIL, для переключения потоков.
0x04 Сводка
существуетCPythonРеализация парсера обусловленаglobal interpreter lockСуществование (глобальной блокировки интерпретации), только один поток может выполняться в любой момент времени.Pythonизbytecode(байт-код).
Общие схемы управления памятью включают подсчет ссылок и сборку мусора,PythonВыбирается первое, что обеспечивает эффективность выполнения одного потока и в то же время упрощает реализацию кодирования. хочу удалитьGILнелегко, даже если успех будетGILудалить, даPythonДругими словами, он жертвует эффективностью выполнения одного потока.
PythonсерединаGILправильноIOИнтенсивные программы могут лучше поддерживать многопоточный параллелизм, ноCPUДля интенсивных программ необходимо использовать несколько процессов или использовать другиеGILпарсер.
В текущей реализации последнего парсера поток принудительно освобождается каждые 0,05 секунды выполнения.GIL, чтобы переключить потоки.
0x05 Чтобы понять GIL, я прочитал следующую информацию
-
docs.Python.org/3.7/glossa народ...
GIL -
docs.Python.org/3.7/glossa люди…
байткодбайткод -
GitHub.com/Python/Использование продукта…
исходный код CPython - wiki.Python.org/MO в/global…
-
realpython.com/python-gil/
What is the Python Global Interpreter Lock (GIL)? - dab EA на .blogspot.com/2010/01/Park Yoochon…
- Купил. Python.org/Piper mail/ Боюсь…
-
Woohoo. YouTube.com/watch?V=о разные…
Understanding the Python GIL - En. Wikipedia.org/wiki/global…