为什么要使用全局翻译锁?


89

Python的Global Interpreter Lock的确切功能是什么?其他编译为字节码的语言是否采用类似的机制?


6
您还应该问“这有关系吗?”
S.Lott

2
我同意,我认为这不是问题,因为在2.6中添加了多处理模块,以允许您以类似线程的方式使用多个进程进行编程。 docs.python.org/library/multiprocessing.html
monkut

Answers:


69

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

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

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

每种方法各有利弊。细粒度锁定允许更大的并行度-两个线程不共享任何资源时可以并行执行。但是,这需要更大的管理开销。对于每一行代码,您可能需要获取并释放几个锁。

相反,粗粒度方法则相反。两个线程不能同时运行,但是单个线程将运行得更快,因为它没有做太多记账工作。最终,它归结为单线程速度和并行性之间的折衷。

曾有几次尝试在python中删除GIL,但单线程计算机的额外开销通常太大。实际上,由于锁争用,即使在多处理器机器上,某些情况实际上也会变慢。

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

它各不相同,可能不应该将其视为语言属性,而应将其视为实现属性。例如,有些Python实现(例如Jython和IronPython)使用其底层VM的线程方法,而不是GIL方法。此外,下一代Ruby似乎正朝着引入GIL迈进


1
您能解释一下:“两个线程不能同时运行”吗?最近,我用Python编写了一个带多线程的简单Web服务器。对于来自客户端的每个新请求,服务器都会为其产生一个新线程,并且这些线程将继续执行。因此,会有多个线程同时运行吗?还是我以错误的方式理解?
2013年

1
@avi AFAIK python线程无法同时运行,但这并不意味着一个线程必须阻塞另一个线程。GIL仅意味着一次只能有一个线程解释python代码,并不意味着线程管理和资源分配不起作用。
Benproductions1 2014年

2
^因此,在任何时间点,只有一个线程将内容提供给客户端...因此,实际使用多线程来提高性能毫无意义。对?
2014年

而且,当然,Java被编译为字节码,并允许非常精细的锁定。
沃伦·露2014年

3
@avi,像Web服务器这样的IO绑定进程仍然可以从Python线程中获得收益。两个或多个线程可以同时执行IO。它们不能同时被解释(CPU)。
品尝

33

以下来自官方的Python / C API参考手册

Python解释器不是完全线程安全的。为了支持多线程Python程序,当前线程必须拥有一个全局锁,才能安全地访问Python对象。如果没有锁,即使是最简单的操作也可能在多线程程序中引起问题:例如,当两个线程同时增加同一对象的引用计数时,引用计数最终只能被增加一次,而不是两次。

因此,存在这样的规则,即只有已获得全局解释器锁的线程才能在Python对象上操作或调用Python / C API函数。为了支持多线程Python程序,解释器会定期释放并重新获取锁-默认情况下,每100个字节码指令(可以通过sys.setcheckinterval()进行更改)。锁定也被释放并重新获得,这可能会阻止潜在的I / O操作(例如,读取或写入文件),以便在请求I / O的线程正在等待I / O操作完成的同时运行其他线程。

我认为这很好地概括了这个问题。


1
我也读过它,但是我不明白为什么Python在这方面不同于Java,例如Java(是吗?)
Federico A. Ramponi

@EliBendersky Python线程被实现为pthreads,并由操作系统(dabeaz.com/python/UnderstandingGIL.pdf)处理,而Java线程是应用程序级线程,其调度由JVM处理
gokul_uf

19

全局解释器锁是一个大的互斥锁,可以防止引用计数器被占用。如果您正在编写纯python代码,则所有操作都在后台进行,但是如果将Python嵌入C中,则可能必须显式获取/释放该锁。

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

另见这个问题


4
“此机制与将Python编译为字节码无关”:确切地说,这是CPython实现的一个产物。其他实现(例如您提到的Jython)可以凭借其线程安全实现
而不

11

像perl 5一样,Python并不是从一开始就设计为线程安全的。事实发生之后,线程就被嫁接了,因此全局解释器锁用于维持互斥,即在给定时间在解释器的肠道中只有一个线程在执行代码。

解释器本身通过频繁地循环锁来使各个Python线程协作执行多任务。

当其他Python线程处于活动状态以“选择加入”此协议并确保没有任何不安全的事情发生时,当您从C与Python交谈时,需要自己抓取锁。

具有单线程遗产的其他系统后来又演变为多线程系统,通常具有这种机制。例如,Linux内核在早期SMP时代就具有“大内核锁定”功能。随着多线程性能逐渐成为一个问题,随着时间的流逝,有一种趋势试图将这些类型的锁分解为更小的碎片,或者用无锁算法和数据结构替换它们,以尽可能提高吞吐量。


+1表示使用粗粒度锁定的事实比大多数人想象的要多,尤其是经常被遗忘的BKL(我使用reiserfs-我对此一无所知的唯一真实原因)。
new123456 2011年

3
Linux使用BKL,自2.6.39版本起,BKL已被完全删除。
2013年

5
当然。请注意,我回答问题后大约三年了。=)
爱德华·KMETT

7

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

在Python中,线程是本地线程,而GIL仅阻止它们在不同的内核上运行。

在Perl中,线程甚至更糟。它们只是复制整个解释器,远没有像Python那样可用。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.