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都是现在比较受关注的并发模型。
文章转自庄周梦蝶 ,原文发布时间 2010-03-23