当前位置: 首页 > 科技观察

图-为什么Python多线程不能发挥多核的优势

时间:2023-03-16 23:17:45 科技观察

本文转载请联系后台技术指南针公众号。1.全局解释器锁如题:为什么Python的多线程不能发挥多核处理器的优势?全局解释器锁(GlobalInterpreterLock)是计算机程序设计语言解释器用来同步线程的一种机制,它在任何时候只允许一个线程在执行。即使在多核处理器上,使用GIL的解释器一次也只允许一个线程执行。使用GIL的常见解释器包括CPython和RubyMRI。由此可见,GIL并不是Python独有的特性。它是解释型语言处理多线程问题的一种机制而不是一种语言特性。2、Python的解释器Python是一种解释器语言,代码是通过解释器来执行的。Python中有很多解释器,是基于不同的语言开发的,每个解释器都有不同的特点。Python程序解释执行过程示意图:CPythonCPython是解释器的主流版本。这个解释器是用C语言写的,也是使用最广泛的解释器。它可以方便地与C/C++类库交互,所以也是最受关注的解释器。Jython是用java语言编写的python解释器。它是一个将python编译成Java字节码然后执行的解释器。它可以轻松地与Java类库进行交互。IronPython将Python代码解释为运行在.Net平台上的字节码来执行,类似于Jython解释器,可以方便地与.Net平台上的类库进行交互。IPython增强了交互效果,但执行过程和功能与CPython相同。PyPy是一种使用JIT(即时)技术的编译器,它注重执行速度,动态编译Python代码,以提高Python的执行速度。在PyPy处理python代码的过程中,有一小部分函数的处理与CPython的执行结果不同。如果你想在你的项目中使用PyPy来提高执行效率,你必须提前了解PyPy和CPython的区别。3、CPython线程不安全CPython线程是操作系统的原生线程,Linux中的pthread完全由操作系统调度执行。pthread本身不是线程安全的,用户需要使用锁来实现多线程安全运行。因此,在CPython解释器下用Python实现多线程,必然也存在线程不安全的问题。这就为多核时代GIL的使用埋下了隐患。4.GIL的背景和挑战Python于1989年由GuidovanRossum发布,当时计算机的主频还没有达到1G,所有的程序都运行在单核计算机上。直到2005年,英特尔才开发出多核处理器。出来。Python版本发布时间表:4.1多核对软件系统的影响GordonMoore在1965年预测,每块集成电路的元件数量每18到24个月就会翻一番,其适用性预计将持续到2015-2020年。在摩尔定律到期之前,软件系统可以简单地依靠硬件的进步来获得性能的提升,或者只需要少量的提升就可以享受性能的飞跃。然而,从2005年开始,时钟速率的增加与晶体管数量的增加并不同步。由于处理器材料的物理性质,时钟速率停滞甚至下降,因此处理器制造商已开始将更多执行单元内核封装到单个芯片中。这种趋势给应用程序开发和编程语言设计带来了越来越大的压力。程序员和编程语言决策者不得不考虑如何快速适配多核硬件以提高软件性能和编程语言市场份额,Python也不例外。4.2多核对CPython的影响在单核时代,Guido崇尚美、明、简。VanRossum选择在解释器层面实现全局互斥锁来保护Python对象,实现单核CPU使用,在单核时代效果很好。如果单核不选择GIL,则需要开发者自行实现任务管理,无法实现CPU利用率的最终提升。图为Python之父GuidovanRossum:但是随着多核时代的到来,高效利用CPU核的一个有效方式就是使用并行。多线程是完全实现并行的好方法,但是CPython的GIL阻碍了CPU多核的发挥。4.3痛并快乐着的GILPython的GIL给用户带来了便利,在GIL的基础上开发了许多重要的包和语言函数。然而,多核CPU的无处不在以及其他语言对Python的冲击,让GIL显得原始粗糙,无法有效利用多核处理器成为弊端。五、多核时代GIL暴露出的问题要了解GIL对多线程程序的影响,必须了解GIL运行的基本原理。在单核CPU的情况下,CPython的Pthread是通过操作系统的调度算法来调度执行的。Python解释器每执行到一定数量的字节码,或者遇到系统IO,就会强行释放GIL,然后触发操作系统的一次线程调度,实现单核CPU的充分利用,释放并重新释放在单核上执行的时间间隔很短。在多核CPU的情况下,多核情况下执行多线程时,一个线程在CPU-A执行完后释放GIL,其他CPU上的线程会竞争,但CPU-A可能会获得GIL立即吉尔。这样一来,其他CPU上被唤醒的线程只能眼睁睁看着CPU-A上的线程再次执行,只能等到切换到被调度的状态。这样会导致多核CPU频繁切换线程,消耗资源,但只有一个线程才能让GIL真正执行Python代码,导致多线程在多核CPU情况下效率不高作为单线程执行。这种情况很像网络编程中多个线程监听同一个端口造成的惊群现象,只不过是在CPU层面,造成的浪费更加奢侈。6.GILI/O密集型的实际影响在单核CPU上执行多线程时,解释器实现有效切换,非常有利。在网络爬虫等I/O密集型程序中,GIL控制下的多线程程序性能并不会像你想象的那么差。CPU密集型对于CPU密集型计算程序GIL来说是个大问题,因为CPU密集型程序不会等待太多,不需要解释器介入,所有任务只能等待一个核,其他核空闲。不可用,所以它看起来非常不适合多核使用。7.放弃和优化GIL一直是有争议的。为此,PEP曾多次尝试删除或优化GIL,但解释器本身的复杂性以及GIL下的众多类库,使得去除GIL成为遥不可及的想法。GIL在1999年从Python1.5中移除,一个免费的线程补丁试图实现这个想法,来自GregStein的补丁。在这个补丁中,GIL被完全移除,取而代之的是细粒度的锁。然而,移除GIL是以单线程程序的执行速度为代价的。单线程执行时,速度降低约40%。使用两个线程显示速度有所提高,但除了这种改进之外,增益并不与内核数量成线性比例。由于执行缓慢,该补丁被拒绝并且大部分被遗忘。多核在1999年还是天方夜谭,但在今天要去掉GIL是极其困难的。目前尚不清楚移除的效果如何。只能说回头太难了。优化GIL2009年,AntoinePitrou在Python3.2中实现了一个新的GIL,并取得了一些积极的成果。这是GIL的一个重大变化。旧的GIL计算Python指令来确定何时删除GIL。一条Python指令将涉及大量工作。在新的GIL实现中,使用固定的超时时间来指示当前线程放弃锁,从而使线程之间的切换更加可预测。8、GIL缺陷的解决方案作为一门生命力极强的流行语言,python在多核时代是不会坐以待毙的。即使有GIL的限制,程序仍然有很多方法可以拥抱多核。多进程Python2.6引入了MultiProcess库,弥补了Threading库中GIL带来的缺陷。在此基础上,开发了多进程程序。每个进程都有一个单独的GIL,避免多个进程之间对GIL的竞争,从而实现多核的使用,但同时也带来了一些同步和通信的问题,这是必然会发生的。CtypesCPython的优势在于与C模块的结合,因此可以使用Ctypes调用C动态库实现计算的传递。C动态库没有GIL来实现多核的利用。Coroutine协程也是一个很好的方法。Python3.4之前是不支持协程的,有一些三方库的实现,比如gevent,Tornado。Python3.4之后,内置的asyncio标准库真正实现了协程的特性。9.总结GIL仍然是Python语言中最难的技术挑战。GIL的问题不是编程语言本身的问题。切换到其他语言只是将问题转移到用户层面。相反,Python的作者试图将这个问题转移到解释器上,向用户呈现一种优雅的语言。虽然多核时代的到来暴露了GIL的缺陷,但Python的决策者和社区开发者已经采取了许多其他措施来拥抱多核,无知地批评GIL是不明智的。正如生产关系需要适应生产力的发展一样,抛开历史背景谈机制的优缺点是有失偏颇的,所以我们必须辩证地对待GIL。