什么是全局解释器锁,为什么它是一个问题?

关于从Python中删除GIL有很多争议,我想了解为什么这是如此重要。我自己从来没有写过编译器或解释器,所以不要吝啬细节,我可能需要他们来理解。


当前回答

当两个线程访问同一个变量时,就有问题了。 例如,在c++中,避免这个问题的方法是定义一些互斥锁,以防止两个线程同时进入一个对象的setter。

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

这意味着如果你为了利用你的多核处理器而开始编写一些多线程代码,你的性能不会提高。 通常的解决方案包括多进程。

请注意,如果您在用C语言编写的方法中,则有可能释放GIL。

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

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

其他回答

当两个线程访问同一个变量时,就有问题了。 例如,在c++中,避免这个问题的方法是定义一些互斥锁,以防止两个线程同时进入一个对象的setter。

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

这意味着如果你为了利用你的多核处理器而开始编写一些多线程代码,你的性能不会提高。 通常的解决方案包括多进程。

请注意,如果您在用C语言编写的方法中,则有可能释放GIL。

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

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

我想分享一个书中的例子多线程的视觉效果。这就是典型的死锁情况

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                      ║
╚═══╩════════════════════════════════════════╩══════════════════════════════════════╝

Python's GIL is intended to serialize access to interpreter internals from different threads. On multi-core systems, it means that multiple threads can't effectively make use of multiple cores. (If the GIL didn't lead to this problem, most people wouldn't care about the GIL - it's only being raised as an issue because of the increasing prevalence of multi-core systems.) If you want to understand it in detail, you can view this video or look at this set of slides. It might be too much information, but then you did ask for details :-)

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

Python 3.7文档

我还想强调Python线程文档中的以下引用:

CPython implementation detail: In CPython, due to the Global Interpreter Lock, only one thread can execute Python code at once (even though certain performance-oriented libraries might overcome this limitation). If you want your application to make better use of the computational resources of multi-core machines, you are advised to use multiprocessing or concurrent.futures.ProcessPoolExecutor. However, threading is still an appropriate model if you want to run multiple I/O-bound tasks simultaneously.

这个链接指向全局解释器锁的Glossary条目,它解释了GIL意味着Python中的线程并行不适合CPU绑定的任务:

The mechanism used by the CPython interpreter to assure that only one thread executes Python bytecode at a time. This simplifies the CPython implementation by making the object model (including critical built-in types such as dict) implicitly safe against concurrent access. Locking the entire interpreter makes it easier for the interpreter to be multi-threaded, at the expense of much of the parallelism afforded by multi-processor machines. However, some extension modules, either standard or third-party, are designed so as to release the GIL when doing computationally-intensive tasks such as compression or hashing. Also, the GIL is always released when doing I/O. Past efforts to create a “free-threaded” interpreter (one which locks shared data at a much finer granularity) have not been successful because performance suffered in the common single-processor case. It is believed that overcoming this performance issue would make the implementation much more complicated and therefore costlier to maintain.

这句话还暗示dicts和变量赋值作为CPython实现细节也是线程安全的:

Python变量赋值是原子的吗? Python字典中的线程安全

接下来,多处理包的文档解释了它如何通过在暴露类似于线程的接口时生成进程来克服GIL:

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

concurrent.futures.ProcessPoolExecutor的文档解释了它使用multiprocessing作为后端:

ProcessPoolExecutor类是Executor的子类,它使用一个进程池来异步执行调用。ProcessPoolExecutor使用多处理模块,这允许它避开全局解释器锁,但也意味着只能执行和返回可pickle对象。

它应该与使用线程而不是进程的其他基类ThreadPoolExecutor形成对比

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

由此我们得出结论:ThreadPoolExecutor只适用于I/O绑定的任务,而ProcessPoolExecutor也可以处理CPU绑定的任务。

进程与线程实验

在Multiprocessing vs Threading Python一文中,我对Python中的进程vs线程做了一个实验分析。

快速预览结果:

其他语言

这个概念似乎也存在于Python之外,同样适用于Ruby,例如:https://en.wikipedia.org/wiki/Global_interpreter_lock

它提到了优点:

提高单线程程序的速度(不需要分别获取或释放所有数据结构上的锁) 轻松集成通常不是线程安全的C库, 易于实现(使用单个GIL比使用无锁解释器或使用细粒度锁的解释器要简单得多)。

但是JVM似乎没有GIL也能做得很好,所以我想知道这样做是否值得。下面的问题问为什么GIL会存在:为什么是全局解释器锁?

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

从http://wiki.python.org/moin/GlobalInterpreterLock

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

如何从Python中删除它?

像Lua,也许Python可以启动多个虚拟机,但Python没有这样做,我猜应该有一些其他的原因。

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