(点击
上方蓝字
,快速关注我们)
编译:伯乐在线 - Coureur
如有好文章投稿,请点击 → 这里了解详情
Python中由于使用了全局解释锁(GIL)的原因,代码并不能同时在多核上并发的运行,也就是说,Python的多线程不能并发,很多人会发现使用多线程来改进自己的Python代码后,程序的运行效率却下降了。这篇文章对Python中的全局解释锁(GIL)进行了介绍。作者认为这是Python中最令人头疼的问题。
十年多年来,Python 的全局解释器锁(GIL)给新手和专家们带来了巨大的挫折感和好奇心。
悬而未决的问题
每个领域都会有这么一个问题:它难度大、耗时多,仅仅是尝试解决这个问题都会让人震惊。整个社区在很久以前就放弃了这个问题,现在只有少数人在努力试图解决它。对于初学者来说,解决这样高难度的问题,会给他带来足够的声誉。计算机科学领域中的 P = NP 就是这样的问题。如果能用多项式时间复杂度解决这个问题,那简直就可以改变世界了。Python 中最困难的问题比 P = NP 要容易一些,不过迄今仍然没有一个满意的答案,解决这个问题和解决 P = NP 问题一样具有革命性。正因为如此, Python 社区会有如此多的人关注于这个的问题: “对于全局解释器锁(GIL)能做什么?”
Python 的底层
要理解 GIL 的含义,我们需要从 Python 的基础说起。像 C++ 这样的语言属于编译型语言,顾名思义,该类型语言的代码输入到编译器,由编译器根据语言的语法进行解析,生成与语言无关的中间表示,最后链接成由高度优化的机器码组成的可执行程序。因为编译器可以获取全部代码(或者是一大段相对独立的代码),所以编译器可以对代码进行深度优化。这使得它可以对不同的语言结构之间的交互进行推理,从而做出更有效的优化。
相反,Python 是解释型语言。代码被输入到解释器来运行。解释器在执行之前对代码一无所知;它只知道 Python 的规则,以及如何在执行过程中动态地应用这些规则。它也有一些优化,但是和编译型语言的优化完全不同。由于解释器不能很好地对代码进行推导,Python 的大部分优化其实是解释器本身的优化。更快的解释器自然意味着更快的程序运行速度,而这种优化对开发者来说是免费的。也就是说,解释器优化后,开发者不用修改 Python 代码就可以坐享优化带来的好处。
这是非常重要的一点,这里有必要在强调一下。在同等条件下,Python 程序的运行速度与解释器的“速度”直接相关相关。无论开发者怎样优化自己的代码,程序的执行速度还是受限于解释器的执行效率。很明显,这就是为什么做了如此多的工作去优化 Python 解释器。这大概是离 Python 开发者最近的免费的午餐。
免费午餐结束了
还是没有结束?摩尔定律告诉了我们硬件提速的时间表,同时,整整一代程序员学会了如何在摩尔定律下编写代码。如果程序员写了比较慢的代码,最简单的办法通常是稍稍等待一下更快的处理器问世即可。事实上,摩尔定律仍然是并且会在很长一段时间内是有效的,不过它生效的方式有了根本的变化。时钟频率不会稳定增长到一个高不可攀的速度,取而代之的是通过多核来利用晶体管密度提高带来的好处。想要程序能够充分利用新处理器的性能,就必须按照并发方式对代码进行重写。
大部分开发者听到“并发”通常会马上想到多线程程序。目前,多线程仍是利用多核系统最常见的方式。多线程编程比传统的“顺序”编程要难很多,不过仔细的程序员可以在代码中充分利用多线程的并发性。既然几乎所有应用广泛的现代编程语言都支持多线程编程,语言在多线程方面的实现应该是事后添加上去的。
意外的事实
现在我们来看一下问题的症结所在。想要利用多核系统,Python 必须支持多线程。作为解释型语言,Python 的解释器对多线程的支持必须是既安全又高效的。我们都知道多线程编程带来的问题。解释器必须避免不同的线程操作内部共享的数据。同时还要保证用户线程能完成尽量多的计算。
那么在不同线程同时访问数据时,怎样才能保护数据呢?答案是全局解释器锁。顾名思义,这是一个加在解释器上的全局锁(从互斥量或者类似意义上来看)。这种方式是很安全,但是(对于 Python 初学者来说)这也就意味着:对于任何 Python 程序,不论有多少线程,多少处理器,任何时候都只有一个线程在执行。
许多人都是偶然发现这个事实。网上的讨论组和留言板充斥着来自 Python 初学者和专家提出的类似的问题:为什么我全新的多线程 Python 程序运行得比其只有一个线程的时候还要慢?在问这个问题时,许多人还觉得自己像个傻瓜,因为如果程序确实是可并行的,那么两个线程的程序显然要比单线程要快。事实上,问及这个问题的次数实在太多了,Python 的专家们已经为它准备了一个标准答案:不要使用多线程,请使用多进程。但这个答案比问题本身更加让人困惑:难道我不能在 Python 中使用多线程?在 Python 这样流行的语言中使用多线程究竟是有多糟糕,连专家都建议不要使用。是我哪里没有搞明白吗?
很遗憾,并不是。由于 Python 解释器的设计,使用多线程以提高性能可以算是一个困难的任务。在最坏的情况下,多线程反而会降低(有时很明显)程序的运行速度。一个计算机科学专业的新生就可以告诉你:当多个线程竞争一个共享资源时将会发生什么。结果通常不理想。很多情况下多线程都能很好地工作,对于解释器的实现和内核开发人员来说,不要对 Python 多线程性能有太多抱怨可能是他们最大的心愿。