原文:Multicore Python: A tough, worthy, and reachable goal
作者: Serdar Yegulalp
翻译:赖信涛
责编:钱曙光
现在的Python有这么多方便优秀的特性,可是有一个特性一直迟迟没有实现:所有基于CPython的解释器都不能同时在多个CPU核心上并行运行App。
这一直是Python最大的绊脚石,特别是现有的实现方法都非常笨拙。目前,随着现在处理器的核心数目不断增长(英特尔最近发布了24核心的CPU),寻求一个长远的解决方案变得更加急不可待。
事实上,在Python中使用多线程是完全可以的——其实用的很多。但是对于CPython来说,不可能的事情是,在多个不同的CPU核心上并行运行不同的线程。在CPython的内存管理中存在安全隐患,所以解释器每次只能运行一个线程,然后根据需要控制锁的全局状态,在不同的线程中切换。
这种锁的机制——解释器全局锁(the Global Interpreter Lock,GIL)是CPython不支持并行线程的根本原因。但是也有一些缓冲的中间件,比如,在硬盘上的IOS操作和网络读取不和GIL绑定,所以可以自由地并行运行他们的线程,但是和CPU绑定的线程就成问题了。
对于Python程序员来说,这就意味着在大型计算任务方面的并行性能的损失。使用Python工作的方便伴随着多线程性能方面的巨大损失,这时,同样比较方便的其他语言就占了优势,比如Google的Go语言。
随着时间的推移,人们相出了很多策略来优化多线程——但大多都是治标不治本——都没有从根本上解决GIL的问题。一个标准的方案是,启动多个CPython实例,然后在这些实例之间共享状态和数据;每一个实例都独立地运行在不同的CPU上。但是Jeff Knupp说,这种方案带来的收益会因为共享状态的成本大大损失。
C语言扩展并不受GIL的限制,所以很多对速度要求很高的库(比如科学计算库Numpy)是用C实现的,可以多核多线程运行。但是CPython的限制依然存在,如果说避免这个问题最好的方法是使用C扩展的话,那么只会让更多的程序员放弃Python而使用C语言。
PyPy,Python的自举实现,采用了JIT技术,虽然没有解决GIL的问题,但是大大提升了代码运行的速度。从某种方面讲,如果只看速度的话,也差不到哪里去,但是从根本上讲,并没有解决多线程的问题。
最后,GIL在Python 3中得到了优化,有了更好的线程切换。但是根本问题依然存在——由于GIL程序还是不能真正地多线程并行运行。
抛开上面这些问题不说,对没有GIL的、兼容现有app的Python的追求从未停止。很多Python的其他实现都完全原离GIL,但是都遭到了性能上的损失。比如,一个运行在JVM之上的Python实现——Jython,使用了JVM的对象跟踪系统,而不是GIL。IronPython采用了和微软的CLR相同的策略。但是两者的性能都很反常。有时候,它们表现的比CPython还慢,有时候不兼容外部的C代码,所以很多程序并不能正常工作。
Trent Nelson of Continuum Analytics的一个叫PyParallel的项目,是一个“实验性的,理论证明可行的对多核心CPU并行运算优化的Python 3设计实现。”它并没有废弃GIL,但是通过替换async
优化了它的引入,所以应用可以使用async
来并行运算(就像IO、网络服务器一样)。这个项目已经停滞好几个月了,但是从文档来看,它的开发者对这样的进度很满意。最终,他们在CPython中这样宣布:“慢点没什么,只要方向对了就可以。”
PyPy的创始人有一个长期的项目,这个Python的实现使用了一种叫“软件内存交换(software transactional memory)”(PyPy-STM)的技术。根据PyPy创始人的说法,好处是,“这个项目可以对现有的程序优化,使没有多线程运行的程序可以在多个核心上运行。”
PyPy-STM听起来像魔法一样,但是它有两个缺点。第一,现在这个项目还在开发中,目前只支持2.X。第二,对于单线程的程序,性能有所下降。根据Python的创始人范·罗苏姆规定的条款,任何有关解决GIL的尝试,都不能降低单线程程序运行的速度。所以,这个项目暂时不能被CPython接受。
Python的核心开发者Larry Hastings在PyCon 2016上分享了一些关于解决GIL问题的观点,Hastings分享了他关于GIL问题的尝试,并在最后给出了一个没有GIL的Python实现,但是因为缓存问题,性能也不尽人意。
Hastings在最后总结到,你可以删掉GIL,但是你必须有一种方法来确保每次只有一个线程在操作全局对象——比如,使用一个公有线程,让解释器来处理状态的改变。
一个好消息是,如果CPython修复了GI了的问题,那么开发者使用Python可以轻松地开发多线程程序。因为Python的语法在不断优化,Python 3.5以来,async/await
语法使得开发多线程程序非常简单。
现在,关于Python GIL的问题还有很多工作需要做。但是毫无疑问的是,问题的第一个答案将是像PyPy-STM一样的单独的Python实现。现在如果想尝试一下没有GIL的Python,可以试一下第三方的版本。CPython目前没有任何反应,期待等待的时间不会太长。