什么是全局解释器锁?为什么会出现问题?
在从Python中删除GIL方面已经产生了很多噪音,我想知道为什么这么重要。我自己从未编写过编译器或解释器,所以不要节俭细节,我可能需要他们理解。
答案 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代码(即使某些面向性能的库可能克服了此限制)。如果希望您的应用程序更好地利用多核计算机的计算资源,建议您使用
multiprocessing
或concurrent.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中的进程与线程进行了实验分析。
结果的快速预览:
答案 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 ║
╚═══╩════════════════════════════════════════╩══════════════════════════════════════╝