Почему некоторые люди говорят, что многопоточность Python безвкусна? Кто-то на Zhihu задал такой вопрос.В нашем здравом смысле многопроцессорность и многопоточность полностью используют аппаратные ресурсы для повышения эффективности работы программы за счет параллелизма.Как они могут стать безвкусными в Python?
Некоторые студенты могут знать ответ, из-за пресловутого GIL в Python, что такое GIL? Почему существует ГИЛ? Действительно ли многопоточность безвкусна? Можно ли удалить GIL? С этими вопросами мы вместе смотрим вниз, и вам нужно немного терпения.
Является ли многопоточность безвкусной?Давайте сначала проведем эксперимент.Эксперимент очень простой,то есть уменьшите число "100 миллионов",и программа завершится,когда оно уменьшится до 0.Если мы используем один поток для выполнения этого задача, какое время выполнения будет? Сколько будет стоить использование многопоточности? покажи мне код
# 任务
def decrement(n):
while n > 0:
n -= 1
один поток
import time
start = time.time()
decrement(100000000)
cost = time.time() - start
>>> 6.541690826416016
На моем компьютере с 4-ядерным процессором время, необходимое для одного потока, составляет 6,5 секунды. Кто-то может спросить, а где нить? На самом деле, когда работает любая программа, по умолчанию будет выполняться основной поток. (Про треды и процессы здесь разворачиваться не буду, открою отдельную статью)
Многопоточность
import threading
start = time.time()
t1 = threading.Thread(target=decrement, args=[50000000])
t2 = threading.Thread(target=decrement, args=[50000000])
t1.start() # 启动线程,执行任务
t2.start() # 同上
t1.join() # 主线程阻塞,直到t1执行完成,主线程继续往后执行
t2.join() # 同上
cost = time.time() - start
>>>6.85541033744812
Создайте два подпотока t1 и t2, и каждый поток выполняет 50 миллионов операций вычитания.После выполнения обоих потоков основной поток завершает программу. В результате на совместное выполнение двух потоков ушло 6,8 секунды, что на самом деле было медленнее. Само собой разумеется, что два потока выполняются на двух процессорах параллельно одновременно, и время должно сокращаться вдвое, а теперь оно не уменьшается, а увеличивается.
В чем причина того, что многопоточность бывает не быстрой, а медленной?
Причина в том, что GIL в интерпретаторе Cpython (основном интерпретаторе языка Python) имеет глобальную блокировку интерпретатора (Global Interpreter Lock), когда интерпретатор интерпретирует и выполняет код Python, блокировка должна быть получена в первую очередь, а это означает, что любой Только один поток может выполнять код одновременно. Если другие потоки хотят получить инструкции кода выполнения ЦП, они должны сначала получить блокировку. Если блокировка занята другими потоками, поток может только ждать, пока поток, который владеет Блокировка.Выполнение кодовых инструкций возможно только при снятой блокировке.
Поэтому выполнение двух потоков вместе медленнее, потому что одновременно работает только один поток, а остальные могут только ждать.Даже при многоядерном процессоре нет возможности для нескольких потоков быть "параллельным" одновременно. Выполнение кода может выполняться только попеременно, поскольку многопоточность включает в себя переключение текста в режиме реального времени и обработку механизма блокировки (получение блокировок, освобождение блокировок и т. д.), поэтому многопоточное выполнение не быстро но медленно.
Когда выйдет GIL?
Когда поток сталкивается с задачей ввода-вывода, GIL высвобождается. GIL также освобождается, когда поток с интенсивными вычислениями (привязанный к ЦП) выполняет 100 тактов интерпретатора (такты можно грубо рассматривать как инструкции виртуальной машины Python). в состоянии пройтиsys.setcheckinterval()
Установите длину шага,sys.getcheckinterval()
Проверьте длину шага. По сравнению с одним потоком, это дополнительные накладные расходы, связанные с многопоточностью.
Почему интерпретатор CPython устроен таким образом?
Многопоточность — это продукт полного использования многоядерных процессоров для адаптации к быстрому развитию современного компьютерного оборудования. Благодаря многопоточности можно эффективно использовать ресурсы ЦП. Python родился в 1991 году. конфигурация была гораздо менее роскошной, чем сегодня.Серверы с 32 ядрами и 64 ГБ памяти не являются обычным явлением, но есть проблема с многопоточностью.Как решить проблемы синхронизации и непротиворечивости общих данных, потому что, когда несколько потоков обращаются к общим данным, могут быть два потока, изменяющие одни данные одновременно В этом случае, если нет подходящего механизма для обеспечения согласованности данных, программа в конечном итоге приведет к исключению Поэтому у отца Python есть глобальная блокировка потока. независимо от того, есть ли у ваших данных проблемы с синхронизацией, это один размер подходит всем.Последняя глобальная блокировка гарантирует безопасность данных. Вот почему многопоточность безвкусна, потому что она не имеет тонкого контроля над безопасностью данных, а решает ее простым и грубым способом.
В 1990-е это решение фактически не было проблемой, ведь аппаратная конфигурация в то время была еще очень простой, одноядерный процессор все еще был мейнстримом, а сценариев применения многопоточности было не так много. время, это все еще было однопоточным способом.Выполнение однопоточного выполнения не предполагает контекстного переключения потоков, но является более эффективным, чем многопоточность (в многоядерной среде это правило не применяется). Следовательно, не обязательно нежелательно использовать метод GIL для обеспечения согласованности и безопасности данных, по крайней мере, в то время это была недорогая реализация.
Так возможно ли удалить GIL?
Есть люди, которые сделали так много, но результаты разочаровывают.В 1999 году два приятеля Грег Стейн и Марк Хаммонд создали форк Python, который удалил GIL и заменил GIL более мелкозернистым для всех изменяемых структур данных. Замок. Однако после тестирования Python без GIL работает почти в 2 раза медленнее в однопоточных условиях.
Отец Python сказал: «Исходя из приведенных выше соображений, удаление GIL без особых усилий не имеет большого смысла.
резюме
Интерпретатор CPython предоставляет GIL (Global Interpreter Lock) для обеспечения синхронизации данных потоков, поэтому с GIL нам все еще нужна синхронизация потоков? Как многопоточность работает в задачах с интенсивным вводом-выводом? Добро пожаловать, чтобы оставить сообщение
Публичный номер: Zen of Python