GIL (Global Interpreter Lock) 全局解释器锁


通常,Python 采用 PIL 解释器锁将应用程序进程,锁定于一物理线程基础之上。

即使是多线程 CPU,一个应用程序进程同时最多也只能使用一个物理线程。

软件层面可以实现多线程,但 Python 仍基于一物理线程。

要真正实现利用多物理线程,软件层面可以使用 Multi-process 替换多线程。

GIL 全局解释器锁


Python 解释器的实现有很多,譬如 C 实现的 CPython、Java 实现的 Jython、Python 实现的 PyPy;其中使用最广泛的是 CPython。

由于采用 C 实现 Python 解释器需要处理复杂线程安全和并发环境内存管理,为降低解释器实现的复杂性,CPython 引入了 GIL (Global Interpreter Lock)。

当使用多线程时,CPython 解释器会创建 GIL,每个线程在执行前会先获取 GIL,阻止其它线程的执行。

在下列情况中,正在运行的 Python 线程会释放自己的 GIL:

  1. 抢占机制
  2. CPU 竟争机制:Unix 系统使用时间片算法,微软 Windows 系统使用抢占式算法。

    CPython 解释器会定期检查 GIL 占用时间,若超过了某个阈值,会强制线程释放 GIL。

    可以采用 sys.setswitchinterval() 设置时间间隔,采用 sys.getswitchinterval() 查看最小时间间隔。

  3. 主动释放
  4. CPython 线程等待 I/O 时,会主动释放 GIL。

GIL 是为方便 CPython 解释器的实现而产生的,保证字节码在执行过程中不会被打断,但并不能保证 Python 程序是线程安全的。譬如:

>>> def add_one(n):
    n += 1
 
>>> import dis
 
>>> dis.dis(add_one)
  2           0 LOAD_FAST                0 (n)
              2 LOAD_CONST               1 (1)
              4 INPLACE_ADD
              6 STORE_FAST               0 (n)
              8 LOAD_CONST               0 (None)
             10 RETURN_VALUE
>>>
					

以上范例中的 2 是行号。

函数 add_one 中的 += 从代码层面来看是一条语句,但是采用 dis 翻译成字节码后,会被分成 INPLACE_ADD 和 STORE_FAST 两条语句。

由于抢占机制的存在,在执行完 INPLACE_ADD 之后解释器可能会执行别的线程,此时若别的线程内也修改了 n 的值,就会出现并发安全性问题。

竟争机制


Unix 系统使用时间片算法,微软 Windows 系统使用抢占式算法,调用物理 CPU 线程。

当一 _thread 在使用某个物理线程,而另一 _thread 也想使用此物理线程,就会形成竟争机制。

若产生的物理线程竟争机制时间较长 (3 秒以上) 且当前物理线程资源被耗尽,就可能导致应用程序异常、卡死,甚至崩溃。

长期占用


不推荐以同步阻塞 (或 3 秒以下短时间 time.sleep() 休眠) 方式,实现自循环 _thread;因为很容易导致其它 _thread,与其产生物理线程竟争机制。

See also:

Python 如向处理 CPU线程 竟争机制

Copyright Notice: This article is exclusive original manuscripts, copyrighted by Happy Digits Software , shall not be reproduced without permission.