Python GIL
GIL Introduction
GIL (Global Interpreter Lock) 是Python解释器(CPython, PyPython)实现中引入的一个全局互斥锁,它确保在任何时刻只有一个Python字节码在被执行。
Python代码在执行前会被编译成字节码,这些字节码是Python虚拟机可以理解的低级指令。源代码(.py) → 字节码(.pyc) → Python虚拟机执行 → 执行结果
为什么引入GIL
简化的内存管理
Python的GC机制是基于引用计数(Reference Counting)。每个Python对象都有一个与之关联的引用计数值,当这个计数值变为0时,对象的内存就会被回收。
1
2
3
4
5
6
# 可以通过 `sys.getrefcount()`函数查看对象的引用计数
>>> import sys
>>> a = "temp"
>>> b = a
>>> sys.getrefcount(a)
3
CPython的reference counting的实现不是thread-safe的,如果没有GIL,多个thread可能会在同时修改某一对象的引用计数时可能会存在Race Condition
- refcount小于实际数量 -> 对象提前被回收 -> Segmentation Fault
- refcount大于实际数量 -> 内存泄露
当时的设计者通过引入GIL确保同一时间只有一个线程能操作Python对象,从而巧妙地规避了这个问题,大大简化了内存管理的设计。
不过在当下多核CPU普及的时代,其作为一种设计取舍也带来了性能上的瓶颈。
便于C扩展集成
CPython大量使用C语言库,但大部分C语言库都不是原生线程安全的。GIL的存在使得C扩展可以在不考虑线程安全的情况下直接操作Python对象。
GIL的作用机制
简言之,GIL是一个全局的互斥锁,线程在执行Python字节码时必须先获取GIL,执行完毕后释放GIL。这样就确保了在任何时刻只有一个线程在执行Python代码。
sequenceDiagram participant T1 as Thread 1 participant T2 as Thread 2 participant GIL T1->>GIL: acquire() activate GIL Note right of T1: GIL acquired T1->>T1: run() T1->>GIL: release() deactivate GIL Note right of T1: GIL released T2->>GIL: acquire() activate GIL Note right of T2: GIL acquired T2->>T2: run() T2->>GIL: release() deactivate GIL Note right of T2: GIL released
CPython中有check_interval
的机制,不同版本的Python对其的实现有所不同。可以是执行一定数量的字节码后自动释放GIL,或者是每个一定时间间隔后释放GIL。从而避免一个线程长时间占用GIL导致其他线程无法执行。
前文提到GIL的存在是为了简化内存管理和C扩展集成,主要是方便这是针对CPython解释器层面的开发者。使用Python编写应用程序时,依然需要注意线程安全问题。
经典的a += 1
操作是非原子操作, 在python字节码中,它被分解为多个步骤,在执行字节码的过程中,如果被其他线程抢占,便有可能导致数据不一致, 类似可以参考Ch2-2ProcessSync
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import dis
counter = 0
def increment():
global counter
counter += 1
dis.dis(increment)
[OUTPUT]
5 RESUME 0
7 LOAD_GLOBAL 0 (counter) # 从全局namespace加载counter的值
LOAD_CONST 1 (1) # 加载const 1
BINARY_OP 13 (+=) # 执行加法并赋值
STORE_GLOBAL 0 (counter) # 将结果存回全局namespace
RETURN_CONST 0 (None) # 返回const None
绕过GIL
PEP 703中给出了关闭GIL的构建配置--disable-gil
,目前是作为实验性功能存在的,后期随着Python的更新会逐步成为默认配置。具体更改可参考PEP 703#overview-of-cpython-changes | peps.python.org
目前多数情况下都不需要考虑GIL的问题,一些对性能要求较高的功能会用其他语言实现,例如C或C++,如果性能需求更高那Python可能不是最佳选择。
另外可以使用多进程(multiprocessing)来绕过GIL的限制,因为每个进程都有一个Interpreter的实例,一定程度上能利用多核CPU的优势。
Python GIL