GIL (Global Interpreter Lock) 全局解释器锁
通常,Python 采用 PIL 解释器锁将应用程序进程,锁定于一物理线程基础之上。
即使是多线程 CPU,一个应用程序进程同时最多也只能使用一个物理线程。
软件层面可以实现多线程,但 Python 仍基于一物理线程。
要真正实现利用多物理线程,软件层面可以使用 多进程 替换多线程。
Python 解释器的实现有很多,譬如 C 实现的 CPython、Java 实现的 Jython、Python 实现的 PyPy;其中使用最广泛的是 CPython。
由于采用 C 实现 Python 解释器需要处理复杂线程安全和并发环境内存管理,为降低解释器实现的复杂性,CPython 引入了 GIL (Global Interpreter Lock)。
当使用多线程时,CPython 解释器会创建 GIL,每个线程在执行前会先获取 GIL,阻止其它线程的执行。
在下列情况中,正在运行的 Python 线程会释放自己的 GIL:
CPU 竟争机制:Unix 系统使用时间片算法,微软 Windows 系统使用抢占式算法。
CPython 解释器会定期检查 GIL 占用时间,若超过了某个阈值,会强制线程释放 GIL。
可以采用 sys.setswitchinterval() 设置时间间隔,采用 sys.getswitchinterval() 查看最小时间间隔。
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,与其产生物理线程竟争机制。
另请参阅:
版权声明: 本文为独家原创稿件,版权归 乐数软件 ,未经许可不得转载。