在 CPython中(注意,在Python 的其他实现中,不存在 GIL机制),Global Interpreter Lock,或者 GIL,是一个互斥锁(mutex),可以保护对 Python 对象的访问,防止多个线程同时执行 Python 字节码(bytecodes)。 GIL 防止 race conditions 并确保线程安全(thread-safy)。GIL 确保在任何时候都只有一个线程在运行。 因为一次只能运行一个线程,所以不能使用多线程编程。简言之,这个互斥锁是必要的,主要是因为 CPython 的内存管理不是线程安全的。
CPython:根据 Wikipedia,“编程语言是用于编写程序的符号,是计算或算法的规范”。 这意味着它只是编写代码的规则和语法。 另外,我们有一个编程语言实现,在大多数情况下,它是实际的解释器或编译器。
Python是一种语言。 CPython 是 Python 在 C 中的实现。Jython 是 Java 中的实现等。
bytecode:字节码的名称源于具有一字节操作码后跟可选参数的指令集。 诸如字节码之类的中间表示可以由编程语言实现输出以简化解释,或者它可以用于通过允许相同的代码在不同设备上跨平台运行来减少对硬件和操作系统的依赖。 字节码通常可以直接在虚拟机(p-code 机器,即解释器)上执行,也可以进一步编译成机器码以获得更好的性能。
线程-安全
线程安全代码仅以不干扰其他线程的方式操作共享数据。
race condition 的一个例子
首先,创建一个变量:
a = 2
现在假设我们有两个线程,thread_one 和 thread_two。 它们执行以下操作:
- thread_one :a = a+2
- thread_two :a = a*3.
如果 thread_one 先访问, thread_two 再访问,结果将是:
- a = 2 + 2,
- .a = 4 * 3
但是,如果发生 thread_two 先运行,然后是 thread_one,我们会得到不同的输出:
- a = 2 * 3
- a = 6 + 2
所以执行顺序显然对输出很重要。 不过,还有更糟糕的可能结果! 如果两个线程同时读取变量 a ,做他们的事情,然后分配新值怎么办? 他们都会看到 a = 2。取决于谁先写出结果,a 最终会是 4 或 6。这不是我们预期的! 这就是我们所说的 race condition。
Race condition:系统行为取决于其他不可控事件的顺序或时间的系统状况。
Race condition 很难发现,尤其是对于不熟悉这些问题的软件工程师。 此外,它们往往是随机发生的,导致不稳定和不可预测的行为。 众所周知,这些错误很难找到和调试。 这正是 Python 拥有 GIL 的原因——让大多数 Python 用户的生活更轻松。
GIL 解决了什么问题
Python 使用引用计数(reference counting)进行内存管理(memory management)。 这意味着在 Python 中创建的对象有一个引用计数变量,该变量跟踪指向该对象的引用数量。 当这个计数达到零时,对象占用的内存被释放。
让我们看一个简短的代码示例来演示引用计数是如何工作的:
import sys
a = []
b = a
print(sys.getrefcount(a))
输出为
3
在上面的示例中,空列表对象 [] 的引用计数为 3。列表对象由 a、b 和传递给 sys.getrefcount()的参数引用。
让我们重新聚焦到 GIL:
问题是这个引用计数变量需要保护免受两个线程同时增加或减少其值的 race condition。如果发生这种情况,可能会导致内存泄漏(leaked memory)而永远不会释放,或者更糟糕的是,在对该对象的引用仍然存在时错误地释放内存。这可能会导致 Python 程序崩溃或其他“奇怪”的错误。
这个引用计数变量可以通过向所有线程共享的数据结构添加锁来保持安全,这样它们就不会被不一致地修改。
但是为每个对象或对象组添加一个锁意味着将存在多个锁,这可能会导致另一个问题——死锁(死锁只有在有多个锁时才会发生)。另一个副作用是由于重复获取和释放锁而导致性能下降。
GIL 是解释器(interpreter)本身的单个锁,它添加了一条规则,即执行任何 Python 字节码都需要获取解释器锁。这可以防止死锁(因为只有一个锁)并且不会引入太多的性能开销。但它有效地使任何受 CPU 限制的 Python 程序成为单线程的。
GIL 虽然被解释器用于 Ruby 等其他语言的解释器,但并不是解决这个问题的唯一方法。某些语言通过使用引用计数以外的方法(garbage collection,垃圾收集)来避免 GIL 对线程安全内存管理的要求。
另一方面,这意味着这些语言通常必须通过添加其他性能提升功能(如 JIT 编译器)来弥补 GIL 单线程性能优势的损失。
参考
https://wiki.python.org/moin/GlobalInterpreterLock
[The Python GIL (Global Interpreter Lock)](https://python.land/python-concurrency/the-
python-gil)
Python vs Cpython
bytecode