Home

cuipf blog

19 Apr 2018

Python全局解释锁(GIL)详解

概述

我们所说的Python全局解释锁(GIL)简单来说就是一个互斥体(或者说锁),这样的机制只允许一个线程来控制Python解释器。 这就意味着在任何一个时间点只有一个线程处于执行状态。GIL对执行单线程任务的程序员们来说并没什么显著影响,但是它成为了计算密集型(CPU-bound)和多线程任务的性能瓶颈。

GIL存在原因

Python利用引用计数来进行内存管理,这就意味着在Python中创建的对象都有一个引用计数变量来追踪指向该对象的引用数量。当数量为0时,该对象占用的内存即被释放。

import sys
a = []
b = a
sys.getrefcount(a)
# output
3

问题在于,在多线程情况下, 这个引用计数变量需要在两个线程同时增加或减少时从竞争条件中得到保护。如果发生了这种情况,可能会导致泄露的内存永远不会被释放,抑或更严重的是当一个对象的引用仍然存在的情况下错误地释放内存。这可能会导致Python程序崩溃或带来各种诡异的bug。

为什么不用锁

通过对跨线程分享的数据结构添加锁来保证数据不会不一致地被修改,这样做可以很好的保证引用计数变量的安全。但是,会存在以下几个问题?

  • 对每一个对象或者对象组添加锁意味着会存在多个锁,会很容易导致死锁(只有当存在多个锁时才会发生)
  • 由于存在大量的锁,重复获取和释放锁会导致性能下降。

所以,GIL相比较锁是一种好的解决方式,GIL是解释器本身的一个单一锁,它增加的一条规则表明任何Python字节码的执行都需要获取解释锁。这有效地防止了死锁(因为只存在一个锁)并且不会带来太多的性能开销。但是这的确使每一个计算密集型任务变成了单线程。

结论

当操作系统还没有线程的概念的时候Python就一直存在着。Python设计的初衷是易于使用以便更快捷地开发,这也使得越来越多的程序员开始使用Python。

GIL是非常容易实现而且很容易添加到Python中。因为只需要管理一个锁所以对于单线程任务来说带来了性能提升。

如何处理GIL

如果GIL给你带来困扰,你可尝试一下方法:

多进程vs多线程

最流行的方法是应用多进程方法,在这个方法中你使用多个进程而不是多个线程。每一个Python进程都有自己的Python解释器和内存空间,因此GIL不会成为问题。Python拥有一个multiprocessing模块可以帮助我们轻松创建多进程;

替代Python解释器

Python中有多个解释器实现办法,分别用C,Java,C#和Python编写的CPython,JPython,IronPython和PyPy是最受欢迎的。GIL只存在于传统的Python实现方法如CPython中。如果你的程序及其库文件可以通过别的实现方式实现,那么你也可以尝试一下。

cuipf

scribble