为什么用GIL编写Python?


112

全局解释器锁(GIL)似乎经常被引用为Python中线程之类的操作比较棘手的主要原因-这就提出了一个问题:“为什么首先要这样做?”

不是程序员,我不知道为什么会这样-放入GIL的逻辑是什么?


10
维基百科的文章指出,“大声展可以是显著障碍并行性付费的具有语言的活力价”,并接着说,“对于使用这种锁的原因包括:提高单线程程序的速度(无需分别获取或释放所有数据结构上的锁),并且易于集成通常不是线程安全的C库。”
罗伯特·哈维

3
@RobertHarvey,活力与它无关。问题是突变。
dan_waterworth 2013年


1
忍不住感觉像Java缺少无符号数字一样,它的目的是防止不知道自己在做什么的人用脚射击。不幸的是,任何人谁知道他们在做什么,得到一个缺乏语言,这是一个真正的耻辱,因为岩石的Python在其他许多方面
基本

1
@Basic必须有某种标准的方法来处理Java中的字节数组(我已经很长时间没有使用它了)才能进行加密数学运算。Python(例如)没有带符号的数字,但是我什至不尝试使用它进行按位运算,因为有更好的方法。
尼克T

Answers:


105

有几种Python实现,例如CPython,IronPython,RPython等。

他们中有些人有GIL,有些人没有。例如,CPython具有GIL:

来自http://en.wikipedia.org/wiki/Global_Interpreter_Lock

可以将用GIL用编程语言编写的应用程序设计为使用单独的进程来实现完全并行性,因为每个进程都有自己的解释器,进而有自己的GIL。

GIL的好处

  • 单线程程序的速度提高。
  • 轻松集成通常不是线程安全的C库。

为什么Python(CPython等)使用GIL

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

GIL之所以引起争议,是因为它在某些情况下阻止多线程CPython程序充分利用多处理器系统。请注意,潜在的阻塞或长时间运行的操作(例如I / O,图像处理和NumPy数字运算)发生在GIL之外。因此,只有在GIL内部花费大量时间解释CPython字节码的多线程程序中,GIL才成为瓶颈。

Python具有GIL而不是细粒度的锁定,原因如下:

  • 在单线程情况下,速度更快。

  • 对于I / O绑定程序,在多线程情况下速度更快。

  • 对于在C库中执行其计算密集型工作的cpu绑定程序,在多线程情况下,速度更快。

  • 它使C扩展更易于编写:除了您允许的地方(即在Py_BEGIN_ALLOW_THREADS和Py_END_ALLOW_THREADS宏之间),Python线程将不会切换。

  • 它使包装C库更加容易。您不必担心线程安全性。如果该库不是线程安全的,则只需在调用GIL时将其锁定即可。

可以通过C扩展来释放GIL。Python的标准库在每个阻塞的I / O调用周围释放了GIL。因此,GIL对I / O绑定服务器的性能没有影响。因此,您可以使用进程(分支),线程或异步I / O在Python中创建网络服务器,而GIL不会妨碍您。

使用GIL发布时,可以类似地调用C或Fortran中的数字库。当您的C扩展等待FFT完成时,解释器将执行其他Python线程。因此,在这种情况下,GIL也比细粒度锁定更容易,更快捷。这构成了大部分数字工作。NumPy扩展会尽可能释放GIL。

线程通常是编写大多数服务器程序的一种坏方法。如果负载较低,则分叉会更容易。如果负载很高,则异步I / O和事件驱动的编程(例如,使用Python的Twisted框架)会更好。使用线程的唯一借口是Windows上缺少os.fork。

仅当您在纯Python中执行CPU密集型工作时,GIL才是问题。在这里,您可以使用流程和消息传递(例如mpi4py)获得更简洁的设计。Python奶酪店中还有一个“处理”模块,该模块为进程提供与线程相同的接口(即,将threading.Thread替换为processing.Process)。

线程可以用来维持GUI的响应性,而与GIL无关。如果GIL损害了您的性能(请参阅上面的讨论),则可以让您的线程产生一个进程并等待其完成。


52
对我来说听起来像酸葡萄。Python无法正确执行线程,因此您要弄清不必要或什至不好的线程的原因。“如果负载很轻,分叉就容易了”,认真吗?仅在您坚持使用引用计数GC的情况下,GIL才能在所有这些情况下“更快”。
Michael Borgwardt

9
s/RPython/PyPy/g。@MichaelBorgwardt给出原因,专业GIL是问题的重点,不是吗?尽管我同意这个答案的某些内容(即关于替代方案的讨论)不重要。不管是好是坏,现在几乎都不可能消除引用-它在整个API和代码库中根深蒂固;如果不重写一半代码并破坏所有外部代码,几乎要摆脱它。

10
不要忘记该multiprocessing库-自2.6起的标准库。对于某些简单类型的并行机制,它的工作池是一个非常漂亮的抽象。
肖恩·麦克索明

8
@alcalde仅当您不知道自己在做什么和/或不希望自己的线程能够协同工作/进行通信时。否则,这会给您带来巨大的痛苦,尤其是考虑到在某些OS上启动新进程的开销。我们的服务器具有32个核心,因此要在CPython中充分利用它们,我需要32个进程。那不是一个“好的解决方案”,而是解决CPython的不足之处的一种方法。
2015年

8
在Windows以外的平台上存在线程的事实应足以证明,在每种情况下分支都不足够。
zneak 2015年

42

首先,Python没有GIL。Python是一种编程语言。编程语言是一组抽象的数学规则和限制。Python语言规范中没有任何内容表明必须有一个GIL。

Python有许多不同的实现。有些有GIL,有些则没有。

拥有GIL的一种简单解释是编写并发代码很困难。通过在代码周围放置一个巨大的锁,您可以强制其始终串行运行。问题解决了!

特别是在CPython中,一个重要的目标是使使用C语言编写的插件扩展解释器变得容易。同样,编写并发代码很困难,因此通过保证不存在并发性,可以更轻松地为口译员。另外,这些扩展中的许多扩展只是现有库的精简包装,而编写这些库时可能并没有考虑到并发性。


6
这与Java缺少无符号数字类型的观点相同-开发人员认为其他所有人都比他们笨...
基本信息

1
@Basic-信不信由你,即使你不是真的很愚蠢,事实证明,拥有一种可以简化假设的语言仍然意味着有用,这些假设意味着您无需考虑某些事情就可以使它们起作用。事情。CPython在某些方面非常有用,包括简单的多线程应用程序(其中的程序是受IO约束的,很多程序是受IO约束的,因此GIL无关紧要),因为使GIL成为最佳解决方案的设计决策也使对这些应用程序的编程更加容易尤其是它支持对集合执行原子操作的事实。
Jules '18

@Jules是的,在您需要这些功能之前,它非常方便。cpython的“首选”解决方案“只需用另一种语言(如c ++)编写”就意味着您将失去所有的python好处。如果您使用C ++编写一半的代码,那么为什么要从Python开始?当然,对于小型API /胶水项目而言,它是快速简便的,而对于ETL,它是首屈一指的,但是它不适合需要大量提升的工作。就像使用Java与硬件进行通讯一样,这简直就是可笑的循环。
基本

16

GIL的目的是什么?

CAPI文档对此有此说法:

Python解释器不是完全线程安全的。为了支持多线程Python程序,有一个全局锁,称为全局解释器锁或GIL,必须由当前线程持有,然后才能安全地访问Python对象。如果没有锁,即使是最简单的操作也可能在多线程程序中引起问题:例如,当两个线程同时增加同一对象的引用计数时,引用计数最终只能被增加一次,而不是两次。

换句话说,GIL可以防止状态损坏。Python程序绝不应该产生分段错误,因为仅允许内存安全操作。GIL将这种保证扩展到了多线程程序。

有哪些选择?

如果GIL的目的是保护国家免遭腐败,那么一个明显的选择是锁定更精细的粮食。也许在每个对象级别。这样做的问题是,尽管已证明它可以提高多线程程序的性能,但它具有更多的开销,因此,单线程程序会受到影响。


2
让用户运行一个带有解释器选项的程序来替换gil,以获得细粒度的锁定,并以某种只读方式知道当前进程是使用gil还是不使用gil引发的。
Luis Masuelli 2014年

尽管有GIL,但由于不小心使用了pyodbc模块,我还是在多线程程序中产生了分段错误。因此,“绝不应该产生分割错误”是谬论。
Muposat
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.