>  기사  >  백엔드 개발  >  파이썬의 GIL이란 무엇입니까? Python의 GIL 소개

파이썬의 GIL이란 무엇입니까? Python의 GIL 소개

不言
不言앞으로
2018-09-30 11:45:234473검색

이 글의 내용은 Python의 GIL이 무엇인지에 관한 것입니다. Python에 GIL을 도입한 것은 참고할만한 가치가 있습니다. 도움이 필요한 친구들이 참고할 수 있기를 바랍니다.

GIL이란 무엇인가요? Python의 인터프리터는 Cpython만이 아닙니다. 인터프리터가 Jpython이면 Python에는 GIL이 없습니다.

공식 설명을 살펴보겠습니다. CPython에서 전역 해석기 잠금(GIL)은 다중 해석을 방지하는

mutex

입니다. 이 잠금은 주로

CPython의 메모리 관리가 스레드로부터 안전하지 않기 때문에 필요합니다.

(그러나 GIL이 존재하기 때문에 다른 기능은 다음과 같은 보장에 의존하게 되었습니다. 강제합니다.)여러 스레드가 동시에 기계 코드를 실행하는 것을 방지하는 뮤텍스(뮤텍스 잠금) 이유는 Cpython의 메모리 관리가 스레드로부터 안전하지 않기 때문입니다#🎜🎜 #whythereisGIL# 🎜🎜#물리적 한계로 인해 CPU 제조업체 간의 코어 주파수 경쟁이 멀티코어로 대체되었습니다. 멀티코어 프로세서의 성능을 보다 효율적으로 활용하기 위해 멀티스레드 프로그래밍이 등장했으며, 이로 인해 스레드 간 데이터 일관성 및 상태 동기화가 어려워졌습니다. CPU 내부의 캐시도 예외는 아니며 여러 캐시 간의 데이터 동기화 문제를 효과적으로 해결하기 위해 다양한 제조업체에서 많은 노력을 기울였으며 이로 인해 필연적으로 특정 성능 손실이 발생했습니다.

물론 파이썬은 탈출할 수 없습니다. 멀티코어의 장점을 활용하기 위해 파이썬은 멀티스레딩을 지원하기 시작했습니다. 여러 스레드 간의

데이터 무결성 및 상태 동기화

를 해결하는 가장 쉬운 방법은 자연스럽게 잠그는 것입니다. 따라서 매우 큰 잠금 GIL이 있으며 점점 더 많은 코드 기반 개발자가 이 설정을 수락하면 이 기능에 크게 의존하기 시작합니다(즉, 기본 Python 내부 개체는 스레드로부터 안전하며 구현할 필요가 없습니다). 추가 메모리 잠금 및 동기화 작업을 고려합니다.

이 구현 방법은 점차 고통스럽고 비효율적이라는 것이 밝혀졌습니다. 그러나 모두가 GIL을 분리하고 제거하려고 시도했을 때 많은 수의 라이브러리 코드 개발자가 GIL에 크게 의존해 왔으며 이를 제거하는 것이 매우 어렵다는 것을 알게 되었습니다. 얼마나 어려운가요? 비유하자면, MySQL과 같은 "소형 프로젝트"는 Buffer Pool Mutex의 큰 잠금을 5.5에서 5.6, 5.7까지 다양한 작은 잠금으로 분할하는 데 거의 5년이 걸렸으며 여전히 계속됩니다. 회사 지원과 그 뒤에 고정된 개발 팀이 있는 제품인 MySQL은 Python과 같은 핵심 개발자 및 코드 기여자로 구성된 고도로 커뮤니티 기반 팀은 말할 것도 없고 그렇게 어려운 시간을 보내고 있습니까?

간단히 말하면 GIL의 존재는 역사적인 이유에 더 가깝습니다. 이 작업을 다시 수행해야 한다면 여전히 멀티스레딩 문제에 직면해야 하지만 적어도 현재 GIL 접근 방식보다 더 우아할 것입니다. GIL의 영향

위의 소개와 공식 정의에 따르면 GIL은 의심할 여지 없이 글로벌 독점 잠금 장치입니다. 전역 잠금의 존재가 멀티스레딩의 효율성에 큰 영향을 미칠 것이라는 점에는 의심의 여지가 없습니다. 마치 Python이 단일 스레드 프로그램인 것과 거의 같습니다. 그러면 독자들은 전역 잠금이 해제되는 한 효율성이 나쁘지 않을 것이라고 말할 것입니다. 시간이 많이 걸리는 IO 작업을 수행할 때 GIL을 해제할 수 있는 한 운영 효율성은 여전히 ​​향상될 수 있습니다. 즉, 아무리 나빠도 싱글 스레드의 효율성보다 나쁘지는 않습니다. 이론상으로는 맞는데 실제로는 그럴까요? 파이썬은 당신이 생각하는 것보다 더 나쁩니다.

멀티스레딩과 싱글스레딩에서 Python의 효율성을 비교해 보겠습니다. 테스트 방법은 매우 간단합니다. 1억 번 반복되는 카운터 함수입니다. 하나는 단일 스레드를 통해 두 번 실행되고, 다른 하나는 여러 스레드를 통해 실행됩니다. 마지막으로 총 실행 시간을 비교합니다. 테스트 환경은 듀얼 코어 Mac Pro입니다. 참고: 스레드 라이브러리 자체의 성능 손실이 테스트 결과에 미치는 영향을 줄이기 위해 여기의 단일 스레드 코드도 스레드를 사용합니다. 단일 스레드를 시뮬레이션하려면 순차적으로 두 번 실행하면 됩니다.

단일 스레드가 순차적으로 실행됨(single_thread.py)

#! /usr/bin/python
from threading import Thread
import time
def my_counter():
i = 0
for _ in range(100000000):
i = i + 1
return True
def main():
thread_array = {}
start_time = time.time()
for tid in range(2):
t = Thread(target=my_counter)
t.start()
t.join()
end_time = time.time()
print("Total time: {}".format(end_time - start_time))
if __name__ == '__main__':
main()

두 개의 동시 스레드가 동시에 실행됨(multi_thread.py)

#! /usr/bin/python
from threading import Thread
import time
def my_counter():
i = 0
for _ in range(100000000):
i = i + 1
return True
def main():
thread_array = {}
start_time = time.time()
for tid in range(2):
t = Thread(target=my_counter)
t.start()
thread_array[tid] = t
for i in range(2):
thread_array[i].join()
end_time = time.time()
print("Total time: {}".format(end_time - start_time))
if __name__ == '__main__':
main()

#🎜 🎜## 🎜🎜#

파이썬은 실제로 멀티스레딩에서 싱글스레딩보다 45% 느린 것을 볼 수 있습니다. 이전 분석에 따르면 GIL 전역 잠금이 존재하더라도 직렬화된 멀티스레딩은 단일 스레딩과 동일한 효율성을 가져야 합니다. 그렇다면 어떻게 그렇게 나쁜 결과가 나올 수 있었을까요?

GIL의 구현 원칙을 통해 그 이유를 분석해 보겠습니다.

현재 GIL 디자인의 결함

파이썬의 GIL이란 무엇입니까? Python의 GIL 소개pcode 수에 따른 스케줄링 방법

Python 커뮤니티의 아이디어에 따르면 스레드 스케줄링은 운영 체제 자체는 이미 매우 성숙해 있으므로 일단 안정적이면 직접 만들 필요가 없습니다. 따라서 Python 스레드는 C 언어의 pthread이며 운영 체제 스케줄링 알고리즘(예: Linux는 CFS)을 통해 예약됩니다. 각 스레드가 CPU 시간을 균등하게 활용할 수 있도록 Python은 현재 실행되는 마이크로코드 수를 계산하고 특정 임계값에 도달하면 GIL을 강제로 해제합니다. 이때 운영체제의 스레드 스케줄링도 함께 작동하게 된다(물론 실제로 컨텍스트 전환이 수행되는지 여부는 운영체제에 따라 결정된다).

의사코드

while True:
acquire GIL
for i in 1000:
do something
release GIL
/* Give Operating System a chance to do thread scheduling */

这种模式在只有一个CPU核心的情况下毫无问题。任何一个线程被唤起时都能成功获得到GIL(因为只有释放了GIL才会引发线程调度)。但当CPU有多个核心的时候,问题就来了。从伪代码可以看到,从release GIL到acquire GIL之间几乎是没有间隙的。所以当其他在其他核心上的线程被唤醒时,大部分情况下主线程已经又再一次获取到GIL了。这个时候被唤醒执行的线程只能白白的浪费CPU时间,看着另一个线程拿着GIL欢快的执行着。然后达到切换时间后进入待调度状态,再被唤醒,再等待,以此往复恶性循环。

PS:当然这种实现方式是原始而丑陋的,Python的每个版本中也在逐渐改进GIL和线程调度之间的互动关系。例如先尝试持有GIL在做线程上下文切换,在IO等待时释放GIL等尝试。但是无法改变的是GIL的存在使得操作系统线程调度的这个本来就昂贵的操作变得更奢侈了。

为了直观的理解GIL对于多线程带来的性能影响,这里直接借用的一张测试结果图(见下图)。图中表示的是两个线程在双核CPU上得执行情况。两个线程均为CPU密集型运算线程。绿色部分表示该线程在运行,且在执行有用的计算,红色部分为线程被调度唤醒,但是无法获取GIL导致无法进行有效运算等待的时间。

파이썬의 GIL이란 무엇입니까? Python의 GIL 소개

由图可见,GIL的存在导致多线程无法很好的立即多核CPU的并发处理能力。

那么Python的IO密集型线程能否从多线程中受益呢?我们来看下面这张测试结果。颜色代表的含义和上图一致。白色部分表示IO线程处于等待。可见,当IO线程收到数据包引起终端切换后,仍然由于一个CPU密集型线程的存在,导致无法获取GIL锁,从而进行无尽的循环等待。

파이썬의 GIL이란 무엇입니까? Python의 GIL 소개

简单的总结下就是:Python的多线程在多核CPU上,只对于IO密集型计算产生正面效果;而当有至少有一个CPU密集型线程存在,那么多线程效率会由于GIL而大幅下降。

如何避免受到GIL的影响

用multiprocessing替代Thread

multiprocessing库的出现很大程度上是为了弥补thread库因为GIL而低效的缺陷。它完整的复制了一套thread所提供的接口方便迁移。唯一的不同就是它使用了多进程而不是多线程。每个进程有自己的独立的GIL,因此也不会出现进程之间的GIL争抢。

当然multiprocessing也不是万能良药。它的引入会增加程序实现时线程间数据通讯和同步的困难。就拿计数器来举例子,如果我们要多个线程累加同一个变量,对于thread来说,申明一个global变量,用thread.Lock的context包裹住三行就搞定了。而multiprocessing由于进程之间无法看到对方的数据,只能通过在主线程申明一个Queue,put再get或者用share memory的方法。这个额外的实现成本使得本来就非常痛苦的多线程程序编码,变得更加痛苦了。

用其他解析器

之前也提到了既然GIL只是CPython的产物,那么其他解析器是不是更好呢?没错,像JPython和IronPython这样的解析器由于实现语言的特性,他们不需要GIL的帮助。然而由于用了Java/C#用于解析器实现,他们也失去了利用社区众多C语言模块有用特性的机会。所以这些解析器也因此一直都比较小众。毕竟功能和性能大家在初期都会选择前者,Done is better than perfect。

总结

Python GIL其实是功能和性能之间权衡后的产物,它尤其存在的合理性,也有较难改变的客观因素。从本分的分析中,我们可以做以下一些简单的总结:

  • 因为GIL的存在,只有IO Bound场景下得多线程会得到较好的性能

  • 如果对并行计算性能较高的程序可以考虑把核心部分也成C模块,或者索性用其他语言实现

  • GIL在较长一段时间内将会继续存在,但是会不断对其进行改进

위 내용은 파이썬의 GIL이란 무엇입니까? Python의 GIL 소개의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 cnblogs.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제