Python的全局解释器锁的作用究竟是什么?编译为字节码的其他语言是否采用类似的机制?
6 回答
一般来说,对于任何线程安全问题,您都需要使用锁来保护您的内部数据结构。这可以通过各种级别的粒度来完成。
您可以使用细粒度锁定,其中每个单独的结构都有自己的锁定。
您可以使用粗粒度锁定,其中一个锁可以保护所有内容(GIL 方法)。
每种方法都有各种优点和缺点。细粒度锁定允许更大的并行性——两个线程在不共享任何资源时可以并行执行。但是,管理开销要大得多。对于每一行代码,您可能需要获取和释放多个锁。
粗粒度方法则相反。两个线程不能同时运行,但是一个单独的线程会运行得更快,因为它没有做太多的簿记。最终归结为单线程速度和并行性之间的权衡。
已经有一些尝试在 python 中删除 GIL,但是单线程机器的额外开销通常太大。由于锁争用,即使在多处理器机器上,某些情况实际上也会变慢。
编译为字节码的其他语言是否采用类似的机制?
它各不相同,它可能不应该被视为一种语言属性,而是一种实现属性。例如,有一些 Python 实现,如 Jython 和 IronPython,它们使用底层 VM 的线程方法,而不是 GIL 方法。此外,Ruby 的下一个版本似乎正朝着引入 GIL 的方向发展。
以下来自官方 Python/C API 参考手册:
Python 解释器不是完全线程安全的。为了支持多线程 Python 程序,当前线程必须持有全局锁才能安全地访问 Python 对象。如果没有锁,即使是最简单的操作也可能在多线程程序中引起问题:例如,当两个线程同时增加同一个对象的引用计数时,引用计数最终可能只增加一次而不是两次。
因此,存在只有获得全局解释器锁的线程才能对 Python 对象进行操作或调用 Python/C API 函数的规则。为了支持多线程 Python 程序,解释器定期释放和重新获取锁——默认情况下,每 100 个字节码指令(可以使用 sys.setcheckinterval() 更改)。锁也会在可能阻塞的 I/O 操作(如读取或写入文件)周围被释放和重新获取,以便其他线程可以在请求 I/O 的线程等待 I/O 操作完成时运行。
我认为它很好地概括了这个问题。
Python 和 perl 5 一样,从一开始就不是为了线程安全而设计的。线程是在事后嫁接的,因此全局解释器锁用于保持互斥,即在给定时间只有一个线程在解释器内部执行代码。
各个 Python 线程由解释器本身通过不时地循环锁来协同处理多任务。
当您从 C 语言与 Python 对话时,当其他 Python 线程处于活动状态以“选择加入”该协议并确保没有任何不安全的事情发生在您背后时,您需要自己抓住锁。
其他具有单线程遗产但后来演变为多线程系统的系统通常具有这种机制。例如,Linux 内核在其早期的 SMP 时代就有“大内核锁”。随着多线程性能逐渐成为一个问题,人们倾向于尝试将这些类型的锁分解成更小的部分,或者在可能的情况下用无锁算法和数据结构替换它们,以最大限度地提高吞吐量。
关于您的第二个问题,并非所有脚本语言都使用它,但这只会降低它们的功能。例如,Ruby 中的线程是绿色的而不是原生的。
在 Python 中,线程是原生的,GIL 只会阻止它们在不同的内核上运行。
在 Perl 中,线程甚至更糟。它们只是复制整个解释器,远不如在 Python 中那样可用。
也许BDFL 的这篇文章会有所帮助。