理解python中的GIL(全局解释器锁)

什么是GIL?
GIL,全称Global Interpreter Lock,中文释义为全局解释器锁,它并不是python语言的一个特性,和python也没有任何关系,它是在实现一种python解释器(CPython)时引入的一个概念。就好比C++是一套语法标准,可以有GCC,Visual C++等等编译器一样,Python也可以通过CPython,Pypy,JPython等不同的编译器来运行。像其中的JPython就没有GIL。

为什么会有GIL?
python使用引用计数来进行内存管理,意味着在Python中创建的对象具有引用计数变量,该变量用于跟踪指向该对象的引用数。当此计数达到零时,释放对象占用的内存。
我们来看下面这段代码:

import sys
a = []
b = a
print(sys.getrefcount(a))  # 3

通过程序的执行结果可以看出空列表的引用次数为3,列表对象由a,b引用并且参数传递给sys.getrefcount()。

那么问题来了,这个引用变量是需要竞争保护机制的,不能让两个线程同时更改它的值,否则会导致内存泄漏。

一种解决办法是给所有跨线程共享的数据结构添加锁,这会导致另一个问题,死锁。而且重复获取和释放锁也会导致性能的下降。

GIL是解释器本身的单个锁,它增加了一条规则,即执行任何Python字节码都需要获取解释器锁。这可以防止死锁(因为只有一个锁)并且不会引入太多的性能开销。但它使任何受CPU限制的Python程序都是单线程的。

对多线程python程序的影响?
当您查看典型的Python程序或任何计算机程序时,那些在性能上受CPU限制的程序与受I / O限制的程序之间存在差异。

CPU绑定程序是那些将CPU推向极限的程序。这包括进行数学计算的程序,如矩阵乘法,搜索,图像处理等。

I / O绑定程序是花费时间等待输入/输出的程序,它可以来自用户,文件,数据库,网络等。I / O绑定程序有时需要等待很长时间才能完成从源获取他们需要的东西,因为源可能需要在输入/输出准备好之前进行自己的处理,例如,用户考虑输入什么输入提示或在其中运行的数据库查询自己的过程。

在多线程版本中,GIL阻止了CPU绑定线程并行执行。

GIL对I / O绑定多线程程序的性能影响不大,因为线程在等待I / O时共享锁。

但是线程完全受CPU限制的程序,例如,使用线程处理部分图像的程序,不仅会因锁定而成为单线程,而且还会看到执行时间的增加,如上例所示,与编写为完全单线程的场景相比,这种增加是由锁添加的获取和释放开销的结果。

如何处理GIL问题?

  • 用多进程代替多线程,但是进程比线程更重,进程管理也有自己的开销
rom multiprocessing import Pool
import time
COUNT = 50000000
def countdown(n):
    while n>0:
        n -= 1
if __name__ == '__main__':
    pool = Pool(processes=2)
    start = time.time()
    r1 = pool.apply_async(countdown, [COUNT//2])
    r2 = pool.apply_async(countdown, [COUNT//2])
    pool.close()
    pool.join()
    end = time.time()
    print('Time taken in seconds -', end - start)
  • 使用其他python解释器,例如 数据分析 常用库 numpy 的 矩阵运算,是由C实现的Python 库 ,所以不需要cpython 解释器,自然不受 GIL的影响

参考文献:async python两个_什么是Python全局解释器锁GIL(Global Interpreter Lock)?


版权声明:本文为m0_54797890原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。