加入开发者社区,探索最新技术趋势与资源共享平台

GIL是什么Python官方解释器(CPython解释器)在内部不是线程安全的,因此它有一个全局解释器锁(GlobalInterpreterLock简称GIL)

问鼎pg官网入口

Python是一种受欢迎的编程工具,使用起来挺方便,不过在进行多线程操作时,它表现出了不足。这其中的主要原因是GIL,也就是全局解释器锁。那么,GIL是如何限制Python多线程的并发能力的?让我们一起探讨这个问题。

GIL是什么

CPython作为Python的官方解释器,其内部并不支持线程安全。为了克服这一缺陷,引入了全局解释器锁,也就是GIL。GIL确保在任何时刻,只有一个线程能够执行Python的字节码。2000年,Python 1.5版本中首次加入了GIL,初衷是为了简化内存管理,并且这一做法一直沿用至今。因此,标准的Python版本无法实现真正的多线程并发。

简言之,Python程序在同一时间只能使用一个CPU核心。即便是在多核服务器上执行多线程的Python程序,本以为能借助多核提升速度,但实际表现并不理想。这主要是因为GIL的存在。就好比多条宽阔的高速公路,却只允许一辆车通行,导致核心计算能力未能得到充分利用。

GIL存在的原因

CPython的内存管理存在线程安全问题,因此GIL变得尤为关键。若一个线程在改变对象的引用计数时,另一线程恰好访问该对象,便可能导致引用计数出现偏差,进而引发内存泄漏或程序崩溃。GIL的作用在于保证同一时间只有一个线程能够操作Python对象。有实验在2015年进行过,结果显示移除GIL后,程序频繁出现崩溃现象。

自从GIL问世以来,Python众多特性都依赖于它所提供的保障。不少第三方库和模块在开发过程中,都预设了同一时间仅有一个线程运行Python字节码。若GIL被移除,这可能会对大量现有代码造成影响,可谓是牵一发而动全身。

GIL带来的问题

以下以GIL问题为例进行说明。存在一个名为count_down的计数函数,该函数用于模拟计算过程。在常规使用中,我们连续调用count_down两次。测试是在一台普通的台式机上进行的,并记录了总耗时。通常情况下,两次调用所需的时间大约是单次调用时间的两倍。然而,在多核处理器上,总耗时并未因多核优势而显著降低。

老板希望提升程序的执行效率。我们自然而然地考虑采用线程技术,让两个函数的调用能够同时进行。按理说,并行执行两次与单次执行所耗费的时间应当相差无几。然而,在2020年的测试中,我们发现采用多线程执行count_down函数并未实现预期的加速效果。两次并行执行与两次顺序执行在时间上几乎没有差异,提速效果并不显著。

是GIL导致的吗

实验表明,多线程并未提升速度,其根本原因在于GIL的存在。尽管我们创建了多个线程,但GIL规定同一时间只能有一个线程执行Python字节码。即便电脑是多核处理器,单个核心也无法同时处理多个线程的Python代码。以四核CPU为例,运行多线程Python程序时,看似四个线程并行,但实际上GIL如同指挥者,仅允许一个线程进行操作。

多个线程为了获得全局解释器锁,频繁地转换执行环境,这个过程是有成本的。随着线程数量上升,转换所需的时间和资源也随之增多,这甚至可能导致程序性能下降。在多核服务器上运行的多线程Python爬虫程序,当线程数量增加时,网页抓取的速度却出现了减慢。

如何解决这个问题

我们可以选择别的Python解析器,比如Jython和IronPython。Jython依托Java虚拟机,IronPython则基于.NET架构,它们都不受全局解释器锁(GIL)的限制。用Jython来执行原本的多线程Python程序,可以实现真正的多线程并行执行。然而,这样做也有不足之处,因为许多Python的第三方库都是针对CPython开发的,在其他的解析器上可能无法正常运行。

代码可以进行改进,以缩短GIL的占用时长。当遇到耗时较长的I/O操作时,线程会主动放弃GIL,允许其他线程运行。在网络爬虫编写过程中,从网络抓取数据属于I/O操作,数据抓取完毕后,再进行本地处理。因此,在等待数据返回期间,可以允许其他线程执行Python代码。

尝试使用multiprocessing模块

Python的multiprocessing模块能通过多个进程来替代多线程,每个进程都配备独立的Python解释器和GIL。这样,在多核处理器上,它们可以并行运行。在多核电脑上运行多进程程序时,每个进程都能独占一个CPU核心。2018年,在一个数据处理项目中,我们用多进程替换了多线程,结果处理速度显著提升。

不过,多进程也存在一些问题。首先,创建进程的成本较高。其次,进程间的通信相对复杂。为了创建进程,系统需要分配新的资源。而且,进程间数据共享并不便捷。以图像处理程序为例,进程间传递图像数据时,需要进行繁琐的序列化和反序列化操作。

各位读者,你们在进行多线程或多进程操作时,是否遇到过难题?欢迎在评论区分享你们的经历,同时别忘了点赞和转发这篇文章。

问鼎pg官网入口