python主流的协程实现有五种:

  • cPython的generator
  • cPython的greenlet
  • cPython的fibers
  • stackless python
  • pypy

除了stackless python和pypy的实现版本之外,其余的协程都实现都无法保存状态。特别是最火的协程框架greenlet也无法保存状态,让人非常惋惜。使用stackless python在公司内部的项目里实现了在一台服务器上跑的任务,中断之后在另外一台服务器上继续执行,非常的awsome!
为什么greenlet的状态无法保存,而其前身stackless python就可以?我们可以先来看一下Java里的协程是如何实现的(http://www.slideshare.net/srirammalhar/communicating-state-machines)

这个是kilim的实现方式。大概的意思就是把栈的每一层frame的局部变量额外保存到协程对应的context里。因为局部变量不过是对heap上对象的引用,所以这些拷贝成本也很低。当前的执行位置使用label的方式标记,用goto可以恢复到之前的执行位置。

最后一个协程的状态要保存的话,就是一个纯Java的context对象做一下序列化就可以了,并不是非常困难。

这种实现协程的方式用stackless python的术语来说就是soft switching。而且kilim的实现也不是最优的,因为其记录的context信息在jvm的内部实现的栈上也有一份。只是jvm没有把这样的执行状态以api的形式暴露出来,使得开发者不得不额外耗费cpu指令来double booking的记录一份同样的信息到另外一个地方。stackless python和pypy就是把自己的内部的栈(形式和kilim的context的实现也差不多,就是frame的状态列表)实现成为可持久化的,所以他们的协程就是可以持久化的。

cPython的generator原理上也是soft switching。其generator内部保存的就是一个python栈的frame(目测3.3的yield from就是保存了一个frame的列表)。但是因为python栈的frame本身无法持久化,所以generator也就无法持久化。理论上来说通过bytecode级别的修改目标函数,可以和kilim一样在python vm之上利用其JMP_ABSOLUTE指令也是可以实现可持久化的协程的(https://docs.python.org/2/library/dis.html#bytecodes http://code.activestate.com/recipes/576944-the-goto-decorator/)。到目前为止,可能是因为stackless python等实现的存在,没有在python中修改bytecode的刚需,也就没有kilim这样的勇夫出现。

另外一类的协程实现是hard switching。所谓hard就是done in hard way的意思。其难度在于,soft switching假设整个要持久化的调用栈全部都是vm内的,比如纯python的。但是如果存在python => c扩展 => python这样的嵌套形式,那么调用栈上就会有c的栈,而不仅仅是python的栈。对于c的栈,我们是无法知道哪些区域是一个变量,而这个变量是一个普通的值,还是一个指向heap上某个位置的指针的。hard switching采取的是暴力地把整个栈区域拷贝到heap上的方式来保存其状态。对于只是switching来说,这样做是足够的了的。即便栈上放的是一个指针,要switch回来的时候把这个指针重新放回栈上就可以了。但是这种实现对于持久化来说是不够的,仅仅保存一个指针,而不保存指针指向的值的话,恢复出来的指针就会指向天晓得的某个区域了。

借一个图(http://www.slideshare.net/saghul/stack-switching-for-fun-and-profit):

hard switching有三份实现

  • stackless python 2.0 是纯hard switching的,3.0改成了soft switching和hard switching混合
  • greenlet 是Armin Rigo经过stackless作者启发,然后写的一个新的真正通用的hard switching的实现
  • pypy 里的 stacklet 是Armin Rigo新写的一个实现,据说更牛x

因为pypy的stacklet库是一个独立的实现,而且非常通用

  • fibers用其来实现了一个类似greenlet的协程框架(Armin Rigo大牛可能不屑于做这样简单的重复包装吧)
  • libgevent 用stacklet和pyuv实现了一个c版本的协程网络编程框架

不管stacklet怎么牛,其本质也是一个hard switching的实现,所以也无法实现状态的保存(本质就是一个调用栈内容的深拷贝)。所以python搞流程引擎,stackless还是不二之选。