为啥要使用全局解释器锁?

Posted

技术标签:

【中文标题】为啥要使用全局解释器锁?【英文标题】:Why the Global Interpreter Lock?为什么要使用全局解释器锁? 【发布时间】:2010-09-20 21:50:12 【问题描述】:

Python的全局解释器锁的作用究竟是什么? 编译成字节码的其他语言是否采用类似的机制?

【问题讨论】:

你也应该问“这有关系吗?” 我同意,我认为这不是问题,因为在 2.6 中添加了多处理模块以允许您以类似线程的方式使用多个进程进行编程。 docs.python.org/library/multiprocessing.html 什么是 Gil:***.com/questions/1294382/… 程序员相关:softwareengineering.stackexchange.com/questions/186889/… 【参考方案1】:

一般来说,对于任何线程安全问题,您都需要使用锁来保护您的内部数据结构。 这可以通过不同级别的粒度来完成。

您可以使用细粒度锁定,其中每个单独的结构都有自己的锁定。

您可以使用粗粒度锁定,其中一个锁可以保护所有内容(GIL 方法)。

每种方法各有利弊。细粒度锁定允许更大的并行性 - 两个线程可以 当它们不共享任何资源时并行执行。但是,管理开销要大得多。为了 每一行代码,你可能需要获取和释放几个锁。

粗粒度方法正好相反。两个线程不能同时运行,但是单个线程会运行得更快,因为它没有做太多的簿记。最终归结为单线程速度和并行性之间的权衡。

已经有一些尝试在 python 中删除 GIL,但单线程机器的额外开销通常太大。即使在多处理器机器上,某些情况实际上可能会更慢 由于锁争用。

编译成字节码的其他语言是否采用类似的机制?

它各不相同,它可能不应该被视为一种语言属性,而是一种实现属性。 例如,有一些 Python 实现,如 Jython 和 IronPython,它们使用其底层 VM 的线程方法,而不是 GIL 方法。此外,Ruby 的下一个版本似乎正在移动 towards 引入 GIL。

【讨论】:

你能解释一下吗:'两个线程不能同时运行'?最近我用 Python 写了一个简单的多线程网络服务器。对于来自客户端的每个新请求,服务器都会为其生成一个新线程,并且这些线程会继续执行。所以会有多个线程同时运行对吗?还是我理解错了? @avi AFAIK python 线程不能同时运行,但这并不意味着一个线程必须阻塞另一个线程。 GIL只是意味着一次只能有一个线程可以解释python代码,并不意味着线程管理和资源分配不起作用。 ^所以在任何时间点,只有一个线程将向客户端提供内容......所以实际上没有必要使用多线程来提高性能。对吗? 当然,Java 被编译成字节码并允许非常细粒度的锁定。 @avi,像网络服务器这样的 IO 绑定进程仍然可以从 Python 线程中获益。两个或多个线程可以同时进行 IO。它们只是不能同时被解释(CPU)。【参考方案2】:

以下来自official Python/C API Reference Manual:

Python 解释器不完整 线程安全。为了支持 多线程 Python 程序, 必须有一个全局锁 由它之前的当前线程持有 可以安全地访问 Python 对象。 没有锁,即使是最简单的 操作可能会导致问题 多线程程序:例如, 当两个线程同时 增加引用计数 同一个对象,引用计数可以 最终只增加一次 而不是两次。

因此,存在的规则只有 获得的线程 全局解释器锁可以操作 Python 对象或调用 Python/C API 职能。为了支持 多线程 Python 程序, 口译员定期发布和 重新获取锁——默认情况下, 每 100 个字节码指令(这 可以改变 sys.setcheckinterval())。锁是 也发布并重新获得了周围 可能阻塞 I/O 操作 就像读取或写入文件一样,所以 其他线程可以运行,而 请求 I/O 的线程是 等待 I/O 操作 完成。

我认为它很好地总结了这个问题。

【讨论】:

我也读过,但我不明白为什么 Python 在这方面与 java 不同(是吗?) @EliBendersky Python 线程被实现为 pthread 并由操作系统 (dabeaz.com/python/UnderstandingGIL.pdf) 处理,而 Java 线程是应用程序级线程,其调度由 JVM 处理【参考方案3】:

全局解释器锁是一个大的互斥锁,可以防止引用计数器被占用。如果您正在编写纯 Python 代码,这一切都发生在幕后,但如果您将 Python 嵌入到 C 中,那么您可能必须显式地获取/释放锁。

此机制与将 Python 编译为字节码无关。 Java 不需要它。其实Jython(python编译成jvm)甚至都不需要。

另见this question

【讨论】:

“此机制与 Python 编译为字节码无关”:准确地说,它是 CPython 实现的产物。其他实现(如您提到的 Jython)可以通过线程安全实现摆脱这种限制【参考方案4】:

Python 和 perl 5 一样,并不是从一开始就设计为线程安全的。线程是事后嫁接的,因此全局解释器锁用于保持互斥,即在给定时间在解释器内部只有一个线程正在执行代码。

各个 Python 线程由解释器本身通过不时地循环锁来协同处理多任务。

当其他 Python 线程处于活动状态以“选择加入”此协议并确保不会在背后发生任何不安全事件时,当您从 C 与 Python 对话时,您需要自己抓住锁。

具有单线程传统但后来演变为多线程系统的其他系统通常具有这种机制。例如,Linux 内核在其早期的 SMP 时代就有“大内核锁”。随着时间的推移,随着多线程性能逐渐成为一个问题,人们倾向于尝试将这些类型的锁分解成更小的部分,或者在可能的情况下用无锁算法和数据结构替换它们,以最大限度地提高吞吐量。

【讨论】:

+1 提到了使用粗粒度锁定的事实比大多数人认为的要好,尤其是经常被遗忘的 BKL(我使用 reiserfs - 这是我所知道的唯一真正原因)。 Linux 有 BKL,从 2.6.39 版本开始,BKL 已被完全删除。 当然。请注意,那是我回答这个问题后的大约 3 年。 =)【参考方案5】:

关于您的第二个问题,并非所有脚本语言都使用此功能,但这只会降低它们的功能。例如,Ruby 中的线程是green 而不是原生的。

在 Python 中,线程是原生的,GIL 只会阻止它们在不同的内核上运行。

在 Perl 中,线程更糟糕。它们只是复制整个解释器,远不如在 Python 中那样可用。

【讨论】:

【参考方案6】:

也许 BDFL 的 this 文章会有所帮助。

【讨论】:

以上是关于为啥要使用全局解释器锁?的主要内容,如果未能解决你的问题,请参考以下文章

GIL(全局解释器锁)

GIL全局解释器锁及协程

Python入门学习-DAY36-GIL全局解释器锁死锁现象与递归锁信号量Event事件线程queue

GIL(全局解释器锁)与互斥锁

为啥使用了线程池速度没有变化呢python

python中的各种锁