Python GIL
GIL Introduction
GIL (Global Interpreter Lock) 是 Python 解释器(CPython, PyPython)实现中引入的一个全局互斥锁,它确保在任何时刻只有一个 Python 字节码在被执行。
Python 代码在执行前会被编译成字节码,这些字节码是 Python 虚拟机可以理解的低级指令。源代码(.py) → 字节码(.pyc) → Python虚拟机执行 → 执行结果
为什么引入 GIL
简化的内存管理
Python 的 GC 机制是基于引用计数(Reference Counting)。每个 Python 对象都有一个与之关联的引用计数值,当这个计数值变为 0 时,对象的内存就会被回收。
# 可以通过 `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 代码。
View Mermaid diagram code
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
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