什么是CPython中的全局解释器锁(GIL)?


244

什么是全局解释器锁,为什么会出现问题?

从Python删除GIL周围产生了很多噪音,我想理解为什么这是如此重要。我自己从未写过编译器或解释器,所以不要节俭,我可能需要理解它们。


3
观看David Beazley告诉您有关GIL的所有信息。
hughdbrown

1
这是一篇冗长的文章,涉及我不久前写的有关GIL和Python线程的文章。它进入细节相当数量的它:jessenoller.com/2009/02/01/...
jnoller

这是一些演示GIL效果的代码:github.com/cankav/python_gil_demonstration
CanKavaklıoğlu2014年

3
我发现这是对GIL的最好解释。请阅读。dabeaz.com/python/UnderstandingGIL.pdf
suhao399 '16

realpython.com/python-gil我发现这很有用
qwr

Answers:


220

Python的GIL旨在序列化从不同线程对解释器内部的访问。在多核系统上,这意味着多个线程无法有效利用多个核。(如果GIL不会导致此问题,那么大多数人就不会在意GIL-只是由于多核系统的普及而成为一个问题。)如果您想详细了解它,您可以观看此视频或查看这组幻灯片。可能信息太多,但是您确实要求提供详细信息:-)

请注意,Python的GIL实际上只是CPython(参考实现)的问题。Jython和IronPython没有GIL。作为Python开发人员,除非您正在编写C扩展,否则通常不会遇到GIL。C扩展编写者需要在其扩展确实阻止I / O时释放GIL,以便Python进程中的其他线程可以运行。


46
好的答案-基本上,这意味着Python中的线程仅用于阻止I / O;您的应用将永远不会超过处理器使用的1个CPU核心
Ana Betts

8
“作为Python开发人员,除非您正在编写C扩展,否则通常不会遇到GIL”-您可能不知道导致多线程代码以蜗牛速度运行的原因是GIL,但是您我一定会感觉到它的影响。仍然让我感到惊讶的是,要利用Python的32核服务器意味着我需要32个进程以及所有相关的开销。
基本

6
@PaulBetts:这是不正确的。这可能是性能关键的代码已经使用了C的扩展,可以做释放GIL例如regexlxmlnumpy模块。Cython允许以自定义代码发布GIL,例如b2a_bin(data)
jfs

5
@Paul Betts:使用多处理模块,可以获得1个以上的CPU使用率的处理器使用率代码。创建多个进程比创建多个线程“重得多”,但是如果您确实需要并行完成工作,则可以选择使用python。
AJNeufeld,2016年

1
@david_adler是的,仍然如此,并且可能会持续一段时间。这并没有真正阻止Python在许多不同的工作负载中真正有用。
Vinay Sajip,

59

假设您有多个线程,它们实际上并没有相互影响对方的数据。那些应该尽可能独立地执行。如果您有一个“全局锁”,您需要获取该“全局锁”以调用(例如)一个函数,那么这最终会成为瓶颈。首先拥有多个线程可能并没有带来太多好处。

用现实世界来类比:假设有100个开发人员在一家公司工作,而他们只有一个咖啡杯。大多数开发人员会花时间等待咖啡而不是编码。

这些都不是特定于Python的-首先,我不知道Python需要GIL的详细信息。但是,希望它使您对一般概念有了更好的了解。


除了等待咖啡杯,这似乎是一个相当受I / O约束的过程,因为他们在等待咖啡杯时可以做其他事情。对于花费大量时间等待的I / O重线程,GIL几乎没有影响。
Cruncher


36

首先让我们了解python GIL提供的功能:

任何操作/指令都在解释器中执行。GIL确保在特定时间由单个线程保存解释器。您的具有多个线程的python程序可在单个解释器中工作。在任何特定时间,此解释器都由单个线程持有。这意味着在任何时刻都只有运行解释器的线程在运行

现在为什么会这样:

您的计算机可能具有多个内核/处理器。多个内核允许多个线程同时执行即多个线程可以在任何特定时间执行。但是,由于解释器由单个线程持有,因此其他线程即使可以访问核心也不会做任何事情。因此,您无法获得多个内核所提供的任何优势,因为在任何时候都只使用一个内核,即当前持有解释器的线程正在使用的内核。因此,您的程序将像执行单线程程序一样花费很长时间。

但是,潜在的阻塞或长时间运行的操作(例如I / O,图像处理和NumPy数字运算)发生在GIL之外。从这里取。因此,对于此类操作,尽管存在GIL,但多线程操作仍将比单线程操作更快。因此,GIL并不总是瓶颈。

编辑:GIL是CPython的实现细节。IronPython和Jython没有GIL,因此他们中应该有一个真正的多线程程序,以为我从未使用过PyPy和Jython,也不确定。


4
注意:PyPy具有GIL参考http : //doc.pypy.org/en/latest/faq.html#does-pypy-have-a-gil-why。虽然Ironpython和Jython没有GIL。
Tasdik Rahman

确实,PyPy具有GIL,但IronPython没有。
伊曼纽尔'18

@Emmanuel编辑了删除PyPy并包含IronPython的答案。
Akshar Raaj

17

Python不允许真正意义上的多线程。它具有多线程程序包,但是如果您想使用多线程来加快代码速度,那么使用它通常不是一个好主意。Python具有称为全局解释器锁(GIL)的构造。

https://www.youtube.com/watch?v=ph374fJqFPE

GIL确保在任何一次只能执行您的一个“线程”。线程获取GIL,做一些工作,然后将GIL传递到下一个线程。这发生得非常快,以至于人眼似乎您的线程正在并行执行,但实际上它们只是使用相同的CPU内核轮流执行。所有这些GIL传递都会增加执行开销。这意味着,如果您想使代码运行更快,那么使用线程包通常不是一个好主意。

有理由使用Python的线程包。如果您想同时运行一些东西,而效率不是问题,那么它就很好而且很方便。或者,如果您正在运行的代码需要等待某些东西(例如某些IO),那么这很有意义。但是线程库不会让您使用额外的CPU内核。

多线程可以外包给操作系统(通过执行多处理),一些调用您的Python代码的外部应用程序(例如Spark或Hadoop)或一些您的Python代码调用的代码(例如:您可以拥有Python代码调用执行昂贵的多线程内容的C函数)。


15

每当两个线程访问同一变量时,您就会遇到问题。例如,在C ++中,避免该问题的方法是定义一些互斥锁,以防止两个线程同时输入一个对象的setter。

在python中可以进行多线程处理,但是不能以比一条python指令更好的粒度同时执行两个线程。正在运行的线程正在获取一个名为GIL的全局锁。

这意味着,如果您开始编写一些多线程代码以利用您的多核处理器,则性能不会提高。通常的解决方法是进行多进程。

请注意,如果您位于用C语言编写的方法中,则可以释放GIL。

GIL的使用不是Python固有的,而是它的某些解释器(包括最常见的CPython)使用的。(#edited,请参阅评论)

GIL问题在Python 3000中仍然有效。


Stackless仍然有一个GIL。Stackless不能改善线程(如模块中的线程)-它提供了另一种编程方法(协程),该方法试图回避问题,但需要非阻塞函数。
jnoller

那么3.2中的新GIL呢?
new123456 2011年

补充一点,如果只有一个线程将更新内存,则您不会有问题/不需要互斥体/信号量。@ new123456可以减少争用并更好地调度线程,而又不损害单线程性能(这本身就令人印象深刻),但它仍然是全局锁定。
2014年

14

Python 3.7文档

我还想强调Python threading文档中的以下引号:

CPython实现细节:在CPython中,由于使用了全局解释器锁,因此只有一个线程可以一次执行Python代码(即使某些面向性能的库可能克服了此限制)。如果您希望应用程序更好地利用多核计算机的计算资源,建议使用multiprocessingconcurrent.futures.ProcessPoolExecutor。但是,如果您要同时运行多个I / O绑定任务,则线程化仍然是合适的模型。

这链接到词汇表条目,global interpreter lock条目解释为GIL暗示Python中的线程并行性不适合CPU绑定的任务

CPython解释器用来确保每次只有一个线程执行Python字节码的机制。通过使对象模型(包括关键的内置类型,如dict)隐式地安全地防止并发访问,从而简化了CPython的实现。锁定整个解释器可以使解释器更容易进行多线程处理,但会牺牲多处理器机器提供的许多并行性。

但是,某些扩展模块(标准的或第三方的)被设计为在执行诸如压缩或散列之类的计算密集型任务时释放GIL。另外,在执行I / O时,始终释放GIL。

过去创建“自由线程”解释器(一种以更精细的粒度锁定共享数据的解释器)的努力并未成功,因为在常见的单处理器情况下性能会受到影响。相信克服该性能问题将使实施更加复杂,因此维护成本更高。

此引号还暗示,作为CPython实现的细节,字典以及变量分配也是线程安全的:

接下来,该软件包文档multiprocessing说明了如何通过生成过程同时暴露类似于以下内容的接口来克服GIL threading

multiprocessing是一个程序包,它使用类似于线程模块的API支持生成程序。多处理程序包同时提供本地和远程并发性,通过使用子进程而不是线程来有效地避开全局解释器锁。因此,多处理模块允许程序员充分利用给定机器上的多个处理器。它可以在Unix和Windows上运行。

以及用于concurrent.futures.ProcessPoolExecutor解释该文档multiprocessing用作后端的文档

ProcessPoolExecutor类是Executor子类,它使用进程池异步执行调用。ProcessPoolExecutor使用多处理模块,该模块可以使其避开全局解释器锁,但也意味着只能执行和返回可拾取对象。

应对比于其他基类ThreadPoolExecutor的是使用线程而不是进程

ThreadPoolExecutor是一个Executor子类,它使用线程池异步执行调用。

从中我们得出结论,ThreadPoolExecutor它仅适用于I / O绑定的任务,同时ProcessPoolExecutor还可以处理CPU绑定的任务。

下面的问题询问为什么GIL首先存在:为什么使用全局解释器锁定?

进程与线程实验

Multiprocessing vs Threading Python中,对Python中的进程与线程进行了实验分析。

快速预览结果:

在此处输入图片说明


0

为什么Python(CPython等)使用GIL

来自http://wiki.python.org/moin/GlobalInterpreterLock

在CPython中,全局解释器锁(即GIL)是一个互斥体,可以防止多个本机线程一次执行Python字节码。该锁是必需的,主要是因为CPython的内存管理不是线程安全的。

如何从Python中删除它?

像Lua一样,也许Python可以启动多个VM,但是python不能这样做,我想应该还有其他原因。

在Numpy或其他一些python扩展库中,有时,将GIL释放到其他线程可以提高整个程序的效率。


0

我想分享《 Visual Effects的多线程》一书中的示例。所以这是经典的死锁情况

static void MyCallback(const Context &context){
Auto<Lock> lock(GetMyMutexFromContext(context));
...
EvalMyPythonString(str); //A function that takes the GIL
...    
}

现在考虑序列中导致死锁的事件。

╔═══╦════════════════════════════════════════╦══════════════════════════════════════╗
    Main Thread                             Other Thread                         
╠═══╬════════════════════════════════════════╬══════════════════════════════════════╣
 1  Python Command acquires GIL             Work started                         
 2  Computation requested                   MyCallback runs and acquires MyMutex 
 3                                          MyCallback now waits for GIL         
 4  MyCallback runs and waits for MyMutex   waiting for GIL                      
╚═══╩════════════════════════════════════════╩══════════════════════════════════════╝
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.