当前位置: 首页 > 后端技术 > Python

python中的GIL

时间:2023-03-26 15:22:29 Python

Python中的GIL是什么?在学习编程时,我们很少涉及多任务处理。但是在python中使用多任务经常会提到GIL锁,那么GIL到底是干什么的呢?有什么好处吗?什么是GIL首先要明确的是,GIL并不是Python的特性,它是在实现Python解析器(CPython)时引入的一个概念。中文翻译是全球翻译锁。为什么会有GIL由于物理限制,CPU厂商之间的核心频率竞争已经被多核所取代。为了更有效地利用多核处理器的性能,出现了多线程编程方式,这带来了线程间数据一致性和状态同步方面的困难。就连CPU内部的Cache也不例外。为了有效解决多个缓存之间的数据同步问题,各厂商花了不少心思,这不可避免地带来了一定的性能损失。Python当然逃不过。为了发挥多核的优势,Python开始支持多线程。_解决多线程间数据完整性和状态同步最简单的方法自然是加锁。_于是就有了GIL这个超级大锁,当越来越多的代码库开发者接受了这个设定后,他们开始严重依赖这个特性(即默认的python内部对象是线程安全的,不需要考虑额外的实现时的内存锁和同步操作)。渐渐地,发现这种实现方式很痛苦,效率也很低。但是当大家尝试拆分和移除GIL时,却发现大量库代码开发者已经严重依赖GIL,想要移除非常困难。有多难?打个比方,像MySQL这样的“小项目”,为了把BufferPoolMutex这个大锁拆分成小锁,从5.5到5.6再到5.7花了将近五年的时间,而且还在继续。MySQL这种有公司背靠,有固定开发团队的产品,都走得这么辛苦,何况Python这种有高度社区化的核心开发和代码贡献者的团队呢?所以简单来说,GIL的存在更多的是历史原因。如果往后推,多线程的问题还是要面对,但至少会比现在的GIL方式优雅一些。GIL的影响接下来我们用一个案例来看一下GIL对python的影响。以上是我们不开启多线程时的运行时间。接下来我们循环相同的次数,看看多线程的运行时间。按理说,我们使用多线程是为了让我们的程序运行起来更加高效,但是我们可以发现时间几乎是一样的。如果我们的数据比较大,可能会变慢,为什么呢?当前GIL设计的缺陷是基于pcodes的数量。GIL作为Cpython中的全局解释器锁,主要作用是保护线程的安全。然后,在同一个线程中,它会先锁定自己,以防止其他线程的执行。为了直观的理解GIL对多线程的性能影响,这里直接借用一张测试结果图(见下图)。该图显示了双核CPU上两个线程的执行情况。两个线程都是CPU密集型计算线程。绿色部分表示线程正在运行并进行有用的计算,红色部分是线程被调度唤醒的等待时间,但是无法获取GIL,导致无法进行有效计算。从图中可以看出,GIL的存在使得多线程无法立即处理多核CPU的并发处理能力。那么Python的IO密集型线程能否从多线程中受益呢?下面我们来看看测试结果。颜色的含义与上图相同。白色部分表示IO线程正在等待。可以看出,当IO线程收到数据包导致终端切换时,由于CPU密集型线程的存在,仍然无法获取到GIL锁,从而进行死循环的等待。简单总结一下就是:Python在多核CPU上的多线程只对IO密集型计算有积极作用;而当至少有一个CPU密集型线程时,由于GIL的存在,多线程的效率会大大降低。但是,GIL不能保证绝对的线程安全。刚才我们提到,为了提高线程的安全性,GIL不需要自己给线程加锁。反正python会直接给我们上锁,让我们更安全。但是,我们来看一个案例:结果是:两个线程分别相加了10万次,我们想要的结果是20万,但是得到的远远小于这个值,因为我们的全局解释器锁不能保证我们的线程安全。原因是我们的GIL不能容忍一个线程一直占用资源,它会依次执行python的其他线程,因为我们的CPU执行速度足够快,可以达到“伪多线程”的效果。总结PythonGIL不是它的特色,而是历史遗留下来的产物,因为最早的计算机大多是单核cpus。python的这个机制是可以理解的,但是随着计算机硬件的发展,我们Python要去掉这个。机制就没那么容易了,因为还有其他框架或者第三方工具使用了这个机制。如果修改了,会导致其他人无法使用。所以我们对此无能为力?当然不是,我们可以使用其他解释器,或者其他封装好的工具类,比如numpy模块,就是用C语言编写的数据分析模块,我们可以无缝使用。