Python GIL锁

一、GIL是什么

在 Python 语言的主流实现 CPython 中,GIL(Global Interpreter Lock,全局解释器锁),在解释器解释执行任何 Python 代码时,都需要先获得这把锁才行,在遇到 I/O 操作时会释放这把锁。如果是纯计算的程序,没有 I/O 操作,解释器会每隔一定次数的操作就释放这把锁,让别的线程有机会执行

(这个次数可以通过sys.setcheckinterval 来调整)。

所以虽然 CPython 的线程库直接封装操作系统的原生线程,但 CPython 进程做为一个整体,同一时间只会有一个获得了 GIL 的线程在跑,其它的线程都处于等待状态等着 GIL 的释放。官网这样解释 GIL:

所以在很多人的概念里CPython就是Python,也就想当然的把GIL归结为Python语言的缺陷。所以这里要先明确一点:GIL并不是Python的特性,Python完全可以不依赖于GIL。

那么CPython实现中的GIL又是什么呢?GIL全称Global Interpreter Lock看一下官方给出的解释:

In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.)

一个防止多线程并发执行机器码的一个Mutex,是个BUG般存在的全局锁

为什么会有GIL

 【这里引用1】

由于物理上得限制,各CPU厂商在核心频率上的比赛已经被多核所取代。为了更有效的利用多核处理器的性能,就出现了多线程的编程方式,而随之带来的就是线程间数据一致性和状态同步的困难。即使在CPU内部的Cache也不例外,为了有效解决多份缓存之间的数据同步时各厂商花费了不少心思,也不可避免的带来了一定的性能损失。

Python当然也逃不开,为了利用多核,Python开始支持多线程。而解决多线程之间数据完整性和状态同步的最简单方法自然就是加锁。 于是有了GIL这把超级大锁,而当越来越多的代码库开发者接受了这种设定后,他们开始大量依赖这种特性(即默认python内部对象是thread-safe的,无需在实现时考虑额外的内存锁和同步操作)。

慢慢的这种实现方式被发现是蛋疼且低效的。但当大家试图去拆分和去除GIL的时候,发现大量库代码开发者已经重度依赖GIL而非常难以去除了。有多难?做个类比,像MySQL这样的“小项目”为了把Buffer Pool Mutex这把大锁拆分成各个小锁也花了从5.5到5.6再到5.7多个大版为期近5年的时间,本且仍在继续。MySQL这个背后有公司支持且有固定开发团队的产品走的如此艰难,那又更何况Python这样核心开发和代码贡献者高度社区化的团队呢?

所以简单的说GIL的存在更多的是历史原因。如果推到重来,多线程的问题依然还是要面对,但是至少会比目前GIL这种方式会更优雅。

 【这里引用2】

相比于使用更细粒度的锁,GIL具有以下优点:

  1. 在单线程任务中更快;
  2. 在多线程任务中,对于I/O密集型程序运行更快;
  3. 在多线程任务中,对于用C语言包来实现CPU密集型任务的程序运行更快;
  4. 在写C扩展的时候更加容易,因为除非你在扩展中允许,否则Python解释器不会切换线程;
  5. 在打包C库时更加容易。我们不用担心线程安全性。,因为如果该库不是线程安全的,则只需在调用GIL时将其锁定即可。

GIL的运作方式

  1. 某个线程拿到GIL
  2. 该线程执行代码,直到达到了check_interval*
  3. 解释器让当前线程释放GIL
  4. 所有的线程开始竞争GIL
  5. 竞争到GIL锁的线程又从第1步开始执行

*注:在Python2中,check_interavl是当前线程遇见IO操作或者ticks计数达到100。在Python3中是执行时间达到阈值(例如15毫秒)。

GIL的影响

因为有GIL的存在,由CPython做解释器(虚拟机)的多线程Python程序只能利用多核处理器的一个核来运行。

如何避免受到GIL的影响

用multiprocess替代Thread,可参看这个代码python函数运行加速_两只蜡笔的小新的博客-CSDN博客_python 加速函数

multiprocess库的出现很大程度上是为了弥补thread库因为GIL而低效的缺陷。它完整的复制了一套thread所提供的接口方便迁移。

唯一的不同就是它使用了多进程而不是多线程。每个进程有自己的独立的GIL,因此也不会出现进程之间的GIL争抢。

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

你可能感兴趣的:(python线程相关,python,开发语言)