前言:我一直在关注如何让开发变得更容易,效率更高,更不易出错。之前做过类似的努力,结合SOA和工作流引擎做的一个流程编辑:http://blog.csdn.net/chgaowei/article/category/597366。它有它自身的缺陷:脚本和程序之间的耦合太大,脚本限制太多。
后来接触并且学习Python,它简洁而强大,应该是解决这类问题的一个思路。那天看到Python自带的生成器yield关键字,感觉着应该是解决逻辑问题的一个突破点(后面会介绍)。再后来参加Python con2011 china,看里面很多人提到了协程和Greenlet,特别是赖勇浩提出的口号:协程才是未来。当时很兴奋,我一直思考的问题有了解决方案。
协程是什么?
可以参考一下维基百科:http://zh.wikipedia.org/wiki/%E5%8D%8F%E7%A8%8B。
我之前写过一篇文章:并发编程学习总结也提到过。我认为这个定义也许更加贴切:“编程语言提供的一种并发机制”。
先让我们来看一下进程和线程。
未来让计算机能够同时处理多个任务,操作系统有了进程的概念,而且在进程内部,基本可以认为当前系统只有一个进程在运行,操作系统对此作了非常好的封装。进程间的切换是有操作系统来完成的。
进程有一个问题,就是进程间切换耗费计算机资源非常大,而且申请一个新的进程的成本也非常高。所以后来就有了线程,它生成的成本和切换的消耗都比进程要低很多,而且线程间通信也非常方便。
线程的问题是:1)存在线程安全问题,出了问题非常不易定位。2)进程内部有线程数目的限制。3)随着并发量的增加,线程生成和切换的成本也变得昂贵。
解决并发还有一个方案是IO多路复用,它的效率确实非常高,但是代码复杂度也非常高:它把一个流程打散成一个个的节点,散落在多个地方,对开发和维护非常不利(这个是我们经常用的方案)。
好,来看协程是如何解决这些问题的:
1)协程的生成成本更低。其实就是一块内存,记录之前的调用的栈信息。你甚至可以通过控制函数调用的层次来进一步降低协程的大小。要生成一个协程,只需要申请一块内存并赋值。
2)切换更快。基本是就是内存的拷贝的速度。
3)没有线程安全问题。一个进程内可以同时存在多个协程,但是只有一个协程是激活的,而且协程的激活和休眠时程序员通过编程来控制,而不是内核来控制的。这样就没有了线程安全问题。
4)可读性更好。相对于IO多路复用来说,你调用的服务接口或者IO接口是异步的,但是你的代码是流畅(顺序)的,并没有被异步和回调打乱。协程也是异步的,但是它会把异步的事件和回调封装起来,形成类似远程调用接口。
一些资料:
赖勇浩在pycon 2011 china上列举了一个node.js用异步实现的一块代码和Python协程风格的代码很能说明问题:http://www.slideshare.net/laiyonghao/python-webgame-10452102
赖勇浩关于协程的文章非常不错,可以作为参考,可惜没有写完:协程三篇之一(协程初接触)。
赖勇浩自己也做了一个机遇Google protocol buffer和Greenlet的远程调用框架abu.rpc,有兴趣的可以参考一下(在写这篇文章之前,还一直没有抽出时间看一下)。
提供协程机制的编程语言:
现在很多语言都提供协程机制,或者通过第三方模块来实现协程。目前据我所知,支持协程的语言包括:Python,lua,erlang,go,io,ruby,c#等。我甚至还看到一些文章会在c中使用go和switch或者longjmp/setjmp来实现协程,不过很难再实际项目中使用。
Python的协程实现:
yield可以实现协程。另外,还有很多第三方的版本,比如greenlet。
协程可以用来做什么?
1)描述逻辑:我主要把协程用来描述逻辑。一个流程可能需要调用多个接口,其中很多接口是异步的。这样描述起来会困难一点。用线程是可以解决部分问题,但是复杂度提升。
2)提高并发:主要应用在IO密集型应用中。gevent就是在greenlet基础之上的一个处理并发的框架,和上面的区别是,这里的事件及接口是IO接口。
缺陷:
无法使用多核。不过可以通过进程+协程来解决。