技术开发 频道

Python并行计算初探

  【IT168 技术】

  注:本文为IT168&NVIDIA联合举办的“如何并行化我的应用”方案征集活动参赛作品。本次方案征集活动详情见:http://cuda.itpub.net/thread-1299715-1-1.html。近期活动的大部分方案,将会逐步与大家分享,不可错过哦!

  CUDA ZONE专区:http://cuda.it168.com/

  CUDA技术论坛:http://cuda.itpub.net

  Python是目前流行的脚本型动态编程语言。与传统的编译型语言如C/C++、Java相比,Python抽象层次高,语法简洁易懂,类库丰富强大,极大地提高了程序员的编程效率。Python脚本通过虚拟机解释执行,运算效率通常只有功能相同的C/C++程序的百分之几甚至更低,为此,Python开放了基于C语言的扩展接口,可以将计算量密集的功能使用较低层的编译型语言实现为动态链接库模块,供Python虚拟机直接调用。Python自带的部分标准库就是使用C语言实现的,从这个角度看,Python虚拟机更像是用来粘合这些高性能扩展模块的胶水。

  Python的标准实现CPython并不是线程安全的。为了正确处理多线程情况,CPython使用了一个“全局锁” (GIL, Global Interpreter Lock),任何线程都必须首先获得GIL才能被调度到虚拟机中执行。每隔若干个虚拟机指令或遇到I/O阻塞的情况,GIL会被自动释放,以此在线程间维持基本机会均等的运行时间。在多核CPU成为主流的今天,这个设计越来越容易受到诟病,GIL的存在使Python管理的多个线程在同一时刻只能有一个运行,其它的CPU核心只能处于空闲状态,计算能力被白白浪费。Python专家也做过一些从CPython中移除GIL的尝试,但得不偿失,完全线程安全的CPython虚拟机的单线程运行效率只能达到现有水平的一半。因此,怎样在GIL存在的前提下发掘Python的并行计算能力,是目前备受关注的问题。

  一个比较容易想到的办法是使用CPython进程代替线程作为CPU调度的单位,进程间通过管道、消息等机制进行通信。在系统级别上,产生一个进程当然比产生一个线程吃力得多,必须维护独立的地址空间和其中包含的所有资源,通信开销也比较高,但确实可以充分利用所有CPU核心的计算能力。多进程架构的天然优势是稳定性,单点失败不容易影响其它工作的正常执行。Python 2.4之前可以通过os.spawn、os.popen等模块衍生进程,Python 2.4提供了更为灵活的subprocess模块。

  循着相同的思路,Python 2.6引入了multiprocessing模块,其中包括进程 (Process)、进程池 (Pool)、进程管理者 (Manager)、管道 (Pipe)、队列 (Queue)、共享对象 (Proxy)、消息传递 (Connection) 以及锁 (Lock)、信号量 (Semaphore) 等常见的同步机制,为快速搭建高质量的多进程并发程序提供了基础。

  GIL是为了保证对Python对象的串行访问而设立的。C扩展模块中能够直接使用操作系统资源,只有在访问Python对象的时候才必须使用GIL。因此,完全可以在C扩展模块中调用平台API来使用系统级别的多线程,被操作系统直接调度到多个CPU核上运行。这种方案的执行效率很高,但必须使用低层次的C扩展接口和系统API,出错的可能也相应增大。诸如SWIG、Cython的自动化扩展生成工具提供了较为简单明了的扩展语法,以后者为例,一个with nogil语句块被自动转换成一对释放GIL (Py_UNBLOCK_THREADS) 和 获取GIL (Py_BLOCK_THREADS) 的操作,单独的函数也可以被声明为要求GIL (with gil) 和不需要GIL (nogil)。

  另外,也可以考虑使用CPython虚拟机之外的Python语言实现,目前比较成熟的包括基于.NET平台的IronPython和基于Java虚拟机的Jython。这两种实现依赖各自成熟的底层运行平台摆脱了GIL的束缚,不仅可以充分利用多核CPU的计算能力,还能够自由调用.NET平台和Java平台上的丰富类库,对Python新版本加入的语法特性也有较好的支持。抛弃标准的CPython的最大问题在于难以调用C扩展模块,使用第三方程序库经常出现兼容性问题,同时它们与CPython的运行效率孰优孰劣,也一直是争议的焦点。

0
相关文章