什么是 Python 全局解释器锁 (GIL)

Python 全局解释器锁(GIL)是一种进程锁,GIL 确保单个进程中一次只有一个线程执行 Python 字节码。

  • 在Python中,单线程进程和多线程进程的性能是相同的,这是因为Python中的GIL。
  • 我们无法在Python中实现多线程,因为我们有全局解释器锁,它限制了线程并作为单线程工作。
  • GIL 可能成为多线程 Python 程序中的瓶颈,特别是在需要并行执行 CPU 密集型任务的情况下。这是因为在任何给定时刻只有一个线程可以执行 Python 字节码。
  • GIL 主要影响并行性(同时执行多个任务),但不影响并发性(同时执行多个任务的外观)。Python 线程对于并发 I/O 密集型任务或涉及等待外部资源的任务仍然有用。

 1992 年Guido van Rossum 为了解决 CPython 解释器中的竞争条件,添加了一个众所周知的锁,称为全局解释器锁(Global Interpreter Lock),简称 GIL。二十五年后,这是我们都喜爱和使用的 Python 解释器的主要缺点之一。

GIL 为 Python 解决了什么问题:
Python 拥有其他语言所没有的东西,那就是引用计数器。借助引用计数器,我们可以计算 python 内部为数据对象赋值的引用总数。由于这个计数器,我们可以对引用进行计数,当计数达到零时,变量或数据对象将自动释放。例如

# Python program showing 
# use of reference counter 
import sys 

geek_var = "Geek"
print(sys.getrefcount(geek_var)) 

string_gfg = geek_var 
print(sys.getrefcount(string_gfg)) 

输出:
4
5

这个引用计数器变量需要受到保护,因为有时两个线程同时增加或减少其值,这样做可能会导致内存泄漏,因此为了保护线程,我们向跨线程共享的所有数据结构添加锁,但有时通过添加锁 存在多个锁会导致另一个问题,即死锁。为了避免内存泄漏和死锁问题,我们在解释器上使用单锁,即全局解释器锁(GIL)。

为什么选择GIL作为解决方案:
Python后端支持C语言,并且Python拥有的所有相关库大多是用C和C++编写的。由于 GIL,Python 提供了一种更好的方法来处理线程安全的内存管理。全局解释器锁在Python中很容易实现,因为它只需要为Python中的处理线程提供一个锁。GIL 实现起来很简单,并且很容易添加到 Python 中。它提高了单线程程序的性能,因为只需要管理一个锁。

对多线程 Python 程序的影响:
当用户编写 Python 程序或任何计算机程序时,受 CPU 限制的程序和受 I/O 限制的程序之间的性能存在差异。CPU 通过同时执行许多操作将程序推向极限,而 I/O 程序必须花费时间等待输入/输出。

代码 1:执行简单倒计时的 CPU 密集型程序

# Python program showing 
# CPU bound program 

import time 
from threading import Thread 

COUNT = 50000000

def countdown(n): 
    while n>0: 
        n -= 1

start = time.time() 
countdown(COUNT) 
end = time.time() 

print('Time taken in seconds -', end - start) 

输出:所用时间(以秒为单位) - 2.5236213207244873


代码2:两个线程并行运行

# Python program showing 
# two threads running parallel 

import time 
from threading import Thread 

COUNT = 50000000

def countdown(n): 
    while n>0: 
        n -= 1

t1 = Thread(target = countdown, args =(COUNT//2, )) 
t2 = Thread(target = countdown, args =(COUNT
//2, )) 

start = time.time() 
t1.start() 
t2.start() 
t1.join() 
t2.join() 
end = time.time() 

print('Time taken in seconds -', end - start) 

输出:所用时间(以秒为单位) - 2.183610439300537

在上面的代码中,CPU 密集型进程和多线程进程的两段代码具有相同的性能,因为在 CPU 密集型程序中,因为 GIL 限制 CPU 只能使用单个线程。CPU 密集型线程和多线程的影响在 python 中是相同的。

为什么 GIL 还没有被删除:
GIL 到目前为止还没有得到改进,因为 python 2 具有 GIL 实现,如果我们在 python 3 中更改它,那么它将给我们带来问题。所以我们不是去掉GIL,而是改进GIL的概念。

目前还没有删除 GIL 的原因之一是 python 后端严重依赖 C,而 C 扩展严重依赖 GIL 的实现方法。

尽管有很多方法可以解决 GIL 解决的问题,但大多数方法都很难实现并且会减慢系统速度。

如何处理Python的GIL:
大多数时候我们使用多处理来防止程序出现GIL。在此实现中,python 为每个进程提供不同的解释器来运行,因此在这种情况下,为多处理中的每个进程提供单线程。

# Python program showing 
# multiprocessing 

import multiprocessing 
import time 

COUNT = 50000000

def countdown(n): 
    while n>0: 
        n -= 1

if __name__ == "__main__"
    # creating processes 
    start = time.time() 
    p1 = multiprocessing.Process(target = countdown, args =(COUNT
//2, )) 
    p2 = multiprocessing.Process(target = countdown, args =(COUNT
//2, )) 

    # starting process 1 
    p1.start() 
    # starting process 2 
    p2.start() 

    # wait until process 1 is finished 
    p1.join() 
    # wait until process 2 is finished 
    p2.join() 
    end = time.time() 
    print('Time taken in seconds -', end - start) 


输出:
所用时间(以秒为单位) - 2.5148496627807617

可以看到,多线程系统和多处理系统所花费的时间没有区别。这是因为多处理系统有自己的问题需要解决。所以这并不能解决问题,但是它提供了 GIL 允许由 python 执行的解决方案。