5.1.3 GIL

我们知道 Python 这种动态语言是由解释器在运行时动态解释并执行的,如果有多个线程同时执行,就意味着有多个解释器也在运行。为了保证解释器自己的线程安全性,有些 Python 解释器(比如最常见的 CPython)采用了一种很暴力的解决方式:全局锁,也就是 Global Interpreter Lock, GIL。再次声明,GIL 不是 Python 的特性,仅仅是特定解释器的特性,比如另一个解释器 JPython 就没有 GIL,不过 CPython 是绝大多数场景下默认的 Python 的解释器,所以有人可能会把 GIL 与 Python 混为一谈。

GIL 最直接的副作用就是严重影响多线程的性能,因为同一时刻只有一个线程能获得锁。GIL 可以用如下伪代码表示:

while True:
    acquire GIL
    for i in range(1000):
        do_something()
    release GIL

当某个线程因为睡眠、IO 或超时释放 GIL 后,从代码中可以看到,它距离再次获得 GIL 仅有一条指令。所以在实际运行时,有很大可能是一个线程不断的释放、获取 GIL,而别的线程一直在等待。由于线程上下文切换存在一定的开销,多个 CPU 密集型的线程同时运行,性能反而比在同一个线程内运行要低。

如果是多个 IO 密集型的线程同时运行,GIL 不会影响性能,因为线程在执行 IO 操作时会主动释放 GIL 锁,因此会出现没有线程获取 GIL 锁(因为大家都在 IO),谁结束了 IO 谁就使用线程的情况。不过需要注意的是,多个 IO 密集型的线程和一个 CPU 密集型线程同时执行时,性能也会受到影响。因为之前解释过,CPU 密集型的线程倾向于一直占有 GIL,导致 IO 密集型线程在 IO 结束后无法立刻获取 GIL,空等一段时间。

具体的测试结果可以参考:Python的GIL是什么鬼,多线程性能究竟如何

如果想要避免 GIL 对性能的影响,有以下几种思路:

  1. 多个 IO 密集型线程不受影响,但不要混入 CPU 密集型线程

  2. 使用 JPython 这样的解释器代替 CPython,但这样做就无法再使用社区已有的 C 语言模块

  3. 使用多进程,多个进程有多个 GIL,自然就互不干扰

Last updated