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

时间:2009-08-18 14:50:07

标签: python python-internals gil

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

在从Python中删除GIL方面已经产生了很多噪音,我想知道为什么这么重要。我自己从未编写过编译器或解释器,所以不要节俭细节,我可能需要他们理解。

8 个答案:

答案 0 :(得分:197)

Python的GIL旨在序列化对来自不同线程的解释器内部的访问。在多核系统上,这意味着多个线程无法有效地利用多个核心。 (如果GIL没有导致这个问题,大多数人都不会关心GIL - 由于多核系统的普及,它只会被提出作为一个问题。)如果你想详细了解它,您可以查看this video或查看this set of slides。这可能是太多的信息,但你确实要求详细信息: - )

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

答案 1 :(得分:55)

假设您有多个线程没有真正触摸彼此的数据。那些应该尽可能独立地执行。如果你有一个“全局锁定”,你需要获取它来(比如说)调用一个函数,这可能最终成为一个瓶颈。首先,你可以从拥有多个线程中获得很多好处。

将其置于现实世界的比喻中:想象100名开发人员只在一家咖啡杯中工作。大多数开发人员都会花时间等待咖啡而不是编码。

这些都不是特定于Python的 - 我不知道Python首先需要GIL的细节。但是,希望它能让您更好地了解一般概念。

答案 2 :(得分:31)

让我们首先了解python GIL提供的内容:

在解释器中执行任何操作/指令。 GIL确保解释器由在特定时刻的单个线程持有。你的多线程python程序在一个解释器中工作。在任何特定时刻,该解释器由单个线程保持。这意味着只有持有解释器的线程任何时刻

现在为什么会出现这个问题:

您的计算机可能有多个核心/处理器。多个核心允许多个线程同时执行 ,即多个线程可以在任何特定时刻执行。 但由于解释器由单个线程持有,因此其他线程即使可以访问核心也没有做任何事情。因此,您没有获得多个内核提供的任何优势,因为在任何时刻,只使用单个内核,即当前持有解释器的线程使用的内核。因此,您的程序执行时间与单线程程序一样长。

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

编辑:GIL是CPython的实现细节。 IronPython和Jython没有GIL,所以我们从来没有使用过PyPy和Jython,也不确定这个程序是真正的多线程程序。

答案 3 :(得分:16)

Python并不允许在最真实的单词中使用多线程。它有一个多线程包,但如果你想多线程来加速你的代码,那么使用它通常不是一个好主意。 Python有一个名为Global Interpreter Lock(GIL)的结构。

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

GIL确保只有一条线程'可以在任何时间执行。一个线程获取GIL,做一点工作,然后将GIL传递到下一个线程。这种情况很快发生,因此对于人眼看来,您的线程似乎并行执行,但它们实际上只是轮流使用相同的CPU核心。所有这些GIL传递都增加了执行的开销。这意味着如果您想让代码运行得更快,那么使用线程包通常不是一个好主意。

有理由使用Python的线程包。如果你想同时运行一些东西,效率不是一个问题,那么它就完全没问题了。或者,如果您正在运行需要等待某些事情的代码(例如某些IO),那么它可能会很有意义。但是线程库不会让你使用额外的CPU核心。

多线程可以外包给操作系统(通过多处理),一些调用Python代码的外部应用程序(例如Spark或Hadoop),或者Python代码调用的一些代码(例如:你可以让你的Python代码调用一个C函数来完成昂贵的多线程事务。)

答案 4 :(得分:14)

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

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

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

请注意,如果您在C中编写的方法中,则可以释放GIL。

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

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

答案 5 :(得分:7)

Python 3.7文档

我还要强调一下Python threading documentation中的以下引号:

  

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

这链接到Glossary entry for global interpreter lock,这说明GIL暗示Python中的线程并行性不适用于CPU bound tasks

  

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

     

但是,某些扩展模块(标准扩展模块或第三方扩展模块)经过设计,以便在执行计算密集型任务(例如压缩或哈希)时释放GIL。另外,在执行I / O时,GIL始终会释放。

     

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

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

接下来,docs for the multiprocessing package解释了它如何通过生成过程克服GIL的同时公开类似于threading的界面:

  

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

docs for concurrent.futures.ProcessPoolExecutor解释说它使用multiprocessing作为后端:

  

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

应与uses threads instead of processes的其他基类ThreadPoolExecutor

对比
  

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

从中可以得出结论,ThreadPoolExecutor仅适用于I / O绑定的任务,而ProcessPoolExecutor也可以处理CPU绑定的任务。

以下问题询问为什么GIL首先存在:Why the Global Interpreter Lock?

进程与线程实验

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

结果的快速预览:

enter image description here

答案 6 :(得分:0)

为什么Python(CPython和其他人)使用GIL

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

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

如何从Python中删除它?

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

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

答案 7 :(得分: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                      ║
╚═══╩════════════════════════════════════════╩══════════════════════════════════════╝