Actor、Coroutine和Continuation的概念澄清


    Actor、 Coroutine和 Continuation这三个概念由于并发的受关注而被经常提到,这里主要想谈下这三者的区别和联系,以便更好的区分问题领域和讨论。首先,Actor和Coroutine在我看来是两种并发模型,仅针对于并发这个领域,而Continuation则是程序设计领域的一个概念,相比于Actor和Coroutine是一个更基础的概念。

    那么,什么是Continuation?这个要从表达式的求值说起。一个表达式的求值可以分为两个阶段:“ What to evaluate?”和“ What to do with the value”,“What to do with the value”就是计算的Continuation。以下面这段代码为例:
if  x < 3  then
   
return  x + 1
else
   
return  x
end

    考察其中的表达式x<3,这个表达式就是“what to evaluate?”,代表你将计算的东西,然后根据x<3的结果决定是执行x+1还是直接返回x,这个根据x<3的值来决定下一步的过程就是这个表达式的Continuation,也就是"what to do with the value"。怎么得到某个表达式的Continuation呢?在支持Continuation的语言里提供了call-with-current-continuation的函数,通常简称为call/cc,使用这个函数你就可以在任何代码中得到Continuation。进一步,continuation有什么作用呢?它可以做的事情不少,如nonlocal exits、回溯、多任务的实现等等。例如在scheme中没有break语句,就可以用call/cc实现一些此类高级的控制结构:

(call / cc ( lambda  ( break )
        (
for - each ( lambda  (x) ( if  ( <  x 0) ( break  x)))
                
' (99 88 -77 66 55))
         # t))


    上面这段代码查找列表(99 88 -77 66 55)中的负数,当查找到的时候马上从迭代中退出并返回该值,其中的break就是一个continuation。刚才还提到continuation可以实现回溯,那么就可以实现一个穷举的机器出来用来搜索解空间,也就是类似Prolog中的回溯机制,在SICP这本书里就介绍了如何用call/cc实现一个简单的逻辑语言系统。更著名的就是神奇的amb操作符,有兴趣可以看看 这里。
    
     接下来我们来看看如何continuation实现多任务,在Continuation的 维基百科里给了一段代码来展示如何用scheme来实现coroutine,我稍微修改了下并添加了注释:
;;continuation栈,保存了等待执行的continuation
(define call/cc call-with-current-continuation)
(define *queue* '())

(define (empty-queue?)
        (null? *queue*))

(define (enqueue x)
        (set! *queue* (append *queue* (list x))))

(define (dequeue)
        (let ((x (car *queue*)))
              (set! *queue* (cdr *queue*))
         x))
;;启动协程
(define (resume proc)
       (call/cc
         (lambda (k)
           ;;保存当前continuation,执行proc
           (enqueue k)
           (proc))))
;;让出执行权
(define (yield)
     (call/cc
      (lambda (k)
         ;;保存当前continuation,弹出上一次执行的cont并执行
        (enqueue k)
        ((dequeue)))))
;;停止当前协程或者当没有一个协程时停止整个程序,最简单的调度程序
(define (thread-exit)
     (if (empty-queue?)
         (exit)
         ((dequeue))))
(注:scheme以分号开头作为注释)

     这其实就是一个coroutine的简单实现,context的保存、任务的调度、resume/yield原语……样样俱全。使用起来类似这样,下面这段程序轮流打印字符串:
(define (display-str str)
        (lambda()
         (let loop()
              (display str)
              (newline)
              (yield)
              (loop))))

;;;创建两个协程并启动调度器
(resume (display-str "This is AAA"))
(resume (display-str "Hello from BBB"))
(thread-exit)

     任务非常简单,打印下传入的字符串并换行,然后让出执行权给另一个任务执行,因此输出:
This is AAA
Hello from BBB
This is AAA
Hello from BBB
This is AAA
Hello from BBB
This is AAA
Hello from BBB
……

    谈了这么多continuation的应用,事实上我想说明的是continuation可以用来实现协程,Ruby 1.9中call/cc和Fiber的实现(在cont.c)大体是一样的同样说明了这一点。

     接下来我们讨论下Actor和Coroutine的关系,上面提到Actor是一种并发模型,我更愿意称之为一种编程风格,Actor跟message passing、Duck Typing是一脉相承的。Actor风格是可以这么描述:将物理世界抽象成一个一个的Actor,Actor之间通过发送消息相互通信,Actor不关心消息是否能被接收或者能否投递到,它只是简单地投递消息给其他actor,然后等待应答。Actor相比于Coroutine是一种更高层次的抽象,它提供的receive和pattern match的原语更接近于现实世界,而使用coroutine编程你还需要手工介入任务调度,这在Actor中是由一个调度器负责的。

    同样,Actor可以用coroutine实现,例如Ruby有个 revactor项目,就是利用1.9引入的Fiber实现actor风格的编程,它的实现非常简单,有兴趣地可以看看,其实跟continuation实现coroutine类似。但是Actor并不是一定要用coroutine才能实现,Actor是一种编程风格,你在Java、C#、C++中同样可以模拟这样的方式去做并发编程,.net社区的老赵 实现过一个简单的Actor, Scala的Actor实现是基于外部库,利用scala强大的元编程能力使得库的使用像内置于语言。

    总结下我想表达的:Continuation是程序设计领域的基础概念,它可以用于实现coroutine式的多任务,Actor是一种比之coroutine更为抽象的编程风格,Actor可以基于Coroutine实现但并非必须,Actor和Coroutine都是现在比较受关注的并发模型。



你可能感兴趣的:(Actor、Coroutine和Continuation的概念澄清)