📄 ch02s02.html
字号:
<html><head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title>2. 较复杂的continuation应用:coroutine</title><link rel="stylesheet" href="html.css" type="text/css"><meta name="generator" content="DocBook XSL Stylesheets V1.69.1"><link rel="start" href="index.html" title="Java网络程序员看Continuation"><link rel="up" href="ch02.html" title="Chapter 2. Continuation,call/cc函数与回退/刷新键"><link rel="prev" href="ch02s01.html" title="1. Continuation初步"><link rel="next" href="ch02s03.html" title="3. 网络框架中用continuation"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">2. 较复杂的continuation应用:coroutine</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch02s01.html">Prev</a> </td><th width="60%" align="center">Chapter 2. Continuation,call/cc函数与回退/刷新键</th><td width="20%" align="right"> <a accesskey="n" href="ch02s03.html">Next</a></td></tr></table><hr></div><div class="section" lang="en"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="d0e176"></a>2. 较复杂的continuation应用:coroutine</h2></div></div></div><p>我们来看一个比较复杂的例子,著名的producer/consumer问题。它是continuation的几个经典用法之一。一个函数,producer,生成一系列object,另一个函数,consumer,处理这些object。这个问题的一个解法是用多线程,UNIX下的pipe就是一个典型的例子。这里我们用continuation在一个线程内解决同样的问题。我们来看Ruby代码:</p><pre class="programlisting"># ------------------------------------# Simple Producer/Consumer# ------------------------------------# Connect a simple counting task and # a printing task together using# continuations.def count_task(count, consumer) (1..count).each do |i| consumer = callcc {|cc| consumer.call cc, i } endenddef print_task() producer, i = callcc {|cc| return cc } while true print "#{i} " producer, i = callcc {|cc| producer.call cc } endenddef count(limit) count_task(limit, print_task()) print "\n"endcount(10)</pre><p>上面的producer是count_task,consumer是print_task,得到的输出是1 2 3 4 5 6 7 8 9 10。我们来简要地看看它是如何工作的。</p><p>在count函数中,我们首先调用print_task()函数。该函数立刻调用callcc。在callcc的closure中,print_task返回至count函数。(注意closure中的return不是导致该closure返回而是导致print_task函数返回)这时我们调用count_task函数,其第二个参数就是print_task执行至第一行的continuation,也即在print_task函数当中的一个快照。接下来的count_task函数是ruby的一种循环语法,翻译成类似Java的语法就是(以下代码不符合任何语言):</p><pre class="programlisting">void count_task(count, consumer) for (i = 1; i <= count; i++) { consumer = callcc {|cc| consumer.call(cc, i) }; }end</pre><p>注意到ruby里的变量不必声明类型。函数count_task会重复十次,每次它会获取当前状态的快照,并将该continuation和i一起传给consumer这个continuation。前面我们看到,consumer存储的是print_task第一行的状态,所以这时print_task第一行的callcc会返回两个值,一个是producer continuation,一个是i。然后print_task进入一个死循环,输出i,并调用producer这个continuation:producer.call。这时我们就又会跳回count_task当中。注意到product.call有一个参数,是print_task的current continuation,所以在count_task中,consumer continuation会改而记载print_task在while true循环中的状态。接下去的过程就比较简单了,count_task和print_task两个函数会互相调用对方的continuation,同时传给对方自己的current continuation,方便对方之后调用自己。您可以多花点时间尽量理解整个过程,其核心的两句代码是count_task的consumer = callcc {|cc| consumer.call cc, i }和print_task里的 producer, i = callcc {|cc| producer.call cc }。</p><p>这段代码可能很难看懂,不过没有关系。正如Ruby的callcc文档中所说,callcc的主要用途之一就是让程序超级难懂。好在凡是涉及callcc的程序都会严格封装其操作,我们几乎永远不需要自己实现这样复杂的结构。</p><p>我们再回头看看count_task和print_task这两个函数。如果一个框架稍微封装一下底层的操作,两个函数可以这样写:</p><pre class="programlisting">def count_task(count, consumer) (1..count).each do |i| resume_other_function(i) endenddef print_task() while true print "#{i} " i = resume_other_function() endend</pre><p>这样就比较好理解了,上面的程序看起来就好像是两个线程(这里是两个函数)在同时执行一样,不过两个线程之间的切换只能手动进行(这种多线程结构被称为coorperative multitasking,以区别于通常的自动切换线程的preemptive multitasking)。神奇的是,两个线程之间可以传递参数和返回值,而且参数和返回值可以是任何对象。</p><p>这种结构有个名字,叫做coroutine,以与通常的subroutine相对应。Ruby语言对它有直接的支持,关键字是yield。C# 2.0声称支持简单的continuation,其实也就是支持coroutine。我们已经看到了continuation可以用来实现coroutine,但是反之则不然。</p><p>与coroutine相比之下,UNIX的pipe就真的是两个线程在同时执行,两者之间可以传递数值(但是UNIX只支持传递字符而非对象。当然,如果配上共享内存,UNIX pipe也可以传递任何对象)。用多线程解producer/consumer问题其实也不容易,有很多同时性的问题。好在和continuation一样,这些处理同时性的底层操作也严格封装起来了。</p><p>后面我们会看到的网络程序部件间的call/answer可以看成一种简单的coroutine,不过在这里我们讲coroutine这个例子主要只是为了展示一下continuation比较复杂的用法。如果您能看懂coroutine的例子,那么您可以停下来想一想,如何用continuation支持后退键?</p></div><div class="navfooter"><hr><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="ch02s01.html">Prev</a> </td><td width="20%" align="center"><a accesskey="u" href="ch02.html">Up</a></td><td width="40%" align="right"> <a accesskey="n" href="ch02s03.html">Next</a></td></tr><tr><td width="40%" align="left" valign="top">1. Continuation初步 </td><td width="20%" align="center"><a accesskey="h" href="index.html">Home</a></td><td width="40%" align="right" valign="top"> 3. 网络框架中用continuation</td></tr></table></div></body></html>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -