让计算机程序并发的运行是一个经常被讨论的话题,今天我想讨论一下Python下的各种并发方式。
并发方式
线程(Thread)
多线程几乎是每一个程序猿在使用每一种语言时都会首先想到用于解决并发的工具(JS程序员请回避),使用多线程可以有效的利用CPU资源(Python例外)。然而多线程所带来的程序的复杂度也不可避免,尤其是对竞争资源的同步问题。
然而在python中由于使用了全局解释锁(GIL)的原因,代码并不能同时在多核上并发的运行,也就是说,Python的多线程不能并发,很多人会发现使用多线程来改进自己的Python代码后,程序的运行效率却下降了,这是多么蛋疼的一件事呀!如果想了解更多细节,推荐阅读这篇文章。实际上使用多线程的编程模型是很困难的,程序员很容易犯错,这并不是程序员的错误,因为并行思维是反人类的,我们大多数人的思维是串行(精神分裂不讨论),而且冯诺依曼设计的计算机架构也是以顺序执行为基础的。所以如果你总是不能把你的多线程程序搞定,恭喜你,你是个思维正常的程序猿:)
Python提供两组线程的接口,一组是thread模块,提供基础的,低等级(Low Level)接口,使用Function作为线程的运行体。还有一组是threading模块,提供更容易使用的基于对象的接口(类似于Java),可以继承Thread对象来实现线程,还提供了其它一些线程相关的对象,例如Timer,Lock
使用thread模块的例子
1 2 3 4 5 |
import thread def worker(): """thread worker function""" print 'Worker' thread.start_new_thread(worker) |
使用threading模块的例子
1 2 3 4 5 6 |
import threading def worker(): """thread worker function""" print 'Worker' t = threading.Thread(target=worker) t.start() |
或者Java Style
1 2 3 4 5 6 7 8 9 10 |
import threading class worker(threading.Thread): def __init__(self): pass def run(): """thread worker function""" print 'Worker' t = worker() t.start() |
进程 (Process)
由于前文提到的全局解释锁的问题,Python下比较好的并行方式是使用多进程,这样可以非常有效的使用CPU资源,并实现真正意义上的并发。当然,进程的开销比线程要大,也就是说如果你要创建数量惊人的并发进程的话,需要考虑一下你的机器是不是有一颗强大的心。
Python的mutliprocess模块和threading具有类似的接口。
1 2 3 4 5 6 7 8 |
from multiprocessing import Process def worker(): """thread worker function""" print 'Worker' p = Process(target=worker) p.start() p.join() |
由于线程共享相同的地址空间和内存,所以线程之间的通信是非常容易的,然而进程之间的通信就要复杂一些了。常见的进程间通信有,管道,消息队列,Socket接口(TCP/IP)等等。
Python的mutliprocess模块提供了封装好的管道和队列,可以方便的在进程间传递消息。
Python进程间的同步使用锁,这一点喝线程是一样的。
另外,Python还提供了进程池Pool对象,可以方便的管理和控制线程。
远程分布式主机 (Distributed Node)
随着大数据时代的到临,摩尔定理在单机上似乎已经失去了效果,数据的计算和处理需要分布式的计算机网络来运行,程序并行的运行在多个主机节点上,已经是现在的软件架构所必需考虑的问题。
远程主机间的进程间通信有几种常见的方式
- TCP/IP
TCP/IP是所有远程通信的基础,然而API比较低级别,使用起来比较繁琐,所以一般不会考虑
- 远程方法调用 Remote Function Call
- 远程对象 Remote Object
远程对象是更高级别的封装,程序可以想操作本地对象一样去操作一个远程对象在本地的代理。远程对象最广为使用的规范CORBA,CORBA最大的好处是可以在不同语言和平台中进行通信。当让不用的语言和平台还有一些各自的远程对象实现,例如Java的RMI,MS的DCOM
Python的开源实现,有许多对远程对象的支持
- 消息队列 Message Queue
比起RPC或者远程对象,消息是一种更为灵活的通信手段,常见的支持Python接口的消息机制有
在远程主机上执行并发和本地的多进程并没有非常大的差异,都需要解决进程间通信的问题。当然对远程进程的管理和协调比起本地要复杂。
Python下有许多开源的框架来支持分布式的并发,提供有效的管理手段包括:
- Celery
Celery是一个非常成熟的Python分布式框架,可以在分布式的系统中,异步的执行任务,并提供有效的管理和调度功能。参考这里
- SCOOP
SCOOP (Scalable COncurrent Operations in Python)提供简单易用的分布式调用接口,使用Future接口来进行并发。
- Dispy
相比起Celery和SCOOP,Dispy提供更为轻量级的分布式并行服务
- PP
PP (Parallel Python)是另外一个轻量级的Python并行服务, 参考这里
- Asyncoro
Asyncoro是另一个利用Generator实现分布式并发的Python框架,
当然还有许多其它的系统,我没有一一列出
另外,许多的分布式系统多提供了对Python接口的支持,例如Spark
伪线程 (Pseudo-Thread)
还有一种并发手段并不常见,我们可以称之为伪线程,就是看上去像是线程,使用的接口类似线程接口,但是实际使用非线程的方式,对应的线程开销也不存的。
- greenlet
greenlet提供轻量级的coroutines来支持进程内的并发。
greenlet是Stackless的一个副产品,使用tasklet来支持一中被称之为微线程(mirco-thread)的技术,这里是一个使用greenlet的伪线程的例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
from greenlet import greenlet def test1(): print 12 gr2.switch() print 34 def test2(): print 56 gr1.switch() print 78 gr1 = greenlet(test1) gr2 = greenlet(test2) gr1.switch() |