coroutine:Coroutine(协程) 模式和 控制 和 行为 的代码复用来源: 发布时间:星期四, 2009年2月12日 浏览:91次 评论:0
概念 协程(Coroutine)这个概念最早是MelvinConway在1963年提出是并发运算中概念指两个子过程通过相互协作完成某个任务用它可以实现协作式多任务协程(coroutine)技术本质上是种控制机制比如消费者/生产者你走几步我走几步;下棋对弈你步我步 Coroutine(协程)可以分为: 非对称式(asymmetric)协程或称半对称式(semi-asymmetric)协程又或干脆就叫半协程(semi-coroutine) 对称式(symmetric)协程 非对称式(asymmetric)协程的所以被称为非对称是它提供了两种传递控制权操作:种是(重)协程(通过coroutine.resume);另种是挂起协程并将控制权返回给协程者(通过coroutine.yield)个非对称协程可以看做是从属于它者 2者关系非常类似于例程(routine)和其者的间关系对称式(symmetric)协程特点是只有种传递控制权操作(coroutine.transfer)即将控制权直接传递给指定协程曾经有这么种说法对称式和非对称式协程机制能力并不等价但事实上很容易根据前者来实现后者在不少动态脚本语言(Python、Perl,Lua,Ruby)都提供了协程或和的相似机制 对称式协程机制可以直接指定控制权传递目标拥有极大自由但得到这种自由代价却是牺牲结构如果稍微复杂点那么即使是非常有经验员也很难对流程有全面而清晰把握这非常类似goto语句它能让跳转到任何想去地方但人们却很难理解充斥着goto非对称式协程具有良好层次化结构关系(重)启动这些协程和个非常类似:被(重)启动协程得到控制权开始执行然后挂起(或结束)并将控制权返回给协程者这和结构化编程风格是完全致 线程和协程异同 协程(Coroutine)类似于线程(Thread)地方是:每个协程都有有自己堆栈,自己局部变量 线程和协程主要区别在于: 1.线程可以并发运行线程的间是不能共写全局变量(写冲突) 2.协程不能并发运行协程的间可以共享全局变量(不会存在写冲突) 协程实现和使用 创建协程 type TMeCoRoutineFunc=procedure(constaCoRoutine:TMeCoRoutine); TMeCoRoutineMethod=procedureofobject; var func:TMeCoroutineFunc; co=TMeCoRoutine.create(func) 参数是个返回值是创建协程对象 协程状态 协程有 3种状态:挂起(suspended)运行(running)停止(dead) 当我们创建个协程时他开始状态为挂起态,也就是说我们创建协程时候不会自动运行 st=co.status; 激活协程 IsSucessful:=co.resume; 激活挂起协程使协程继续运行参数co是个协程对象 如果协程是挂起状态则继续运行resume返回true如果协程已经停止或者遇到其他resume返回false 挂起协程 co.yield([...]); 挂起当前协程直到协程被外部协程使用CoRoutine.Resume再次激活将返回到执行CoRoutine.Yield后地方继续执行 CoRoutine.yield参数将传递给SaveYieldedValue虚思路方法你需要重载该思路方法处理 当个协程正在运行时,不能在外部终止它.只能在协程内部coroutine.yield挂起当前协程 不需要考虑协程安全、协程同步问题协程代码比线程代码更容易编写 “控制”和“行为”复用 在很多时候我们需要对数据结构(如:ListStack)中元素按某种要求进行遍历我们称的为“控制”;然后对目标元素进行某个操作(如显示该元素)我们称的为“行为”许多情况下这种“控制”或行为代码本来是可以被复用但是难以将这其中“控制”和“行为”分离造成了我们不得不遍又遍书写这些类似代码(虽然利用回调可以实现在定程度上“控制”和行为分离但是并不优雅也不无法实现彻底重用) 生产和消费 让我们先看下面段代码producer过程(生产者)产生些数值(根据要求进行遍历)而Consumer过程(消费者)则处理值(对目标元素进行操作): procedureproducer; var i:eger; [Page] begin fori:=0to100do imod5=0thenconsumer(i); end; procedureConsumer(constvalue:eger); begin writeln(value); end; 请注意在生产者(producer)消费过程(consumer)这里出现了耦合该生产者只能为这个Cosumer过程服务我们希望生产者过程(producer)能增强通用性降低耦合度能为区别消费者服务 ok我们想到了回调: type TComsumerCallback:procedure(constvalue:eger); procedureproducer(constaCallBack:TComsumerCallback); var i:eger; begin fori:=0to100do//循环枚举控制 imod5=0thenaCallBack(i); end; 好了producer可以为区别消费者服务了但是新问题又出来如果我们现在还希望仅当消费者要求值时候才去生产者取得值呢?实现控制和行为彻底分离象这样: procedureMainConsumer; begin //控制在MyProducer中控制复用 foriinMyProducerdo//当消费者要求值时候才去 begin Consumer(i); .... //随时可以停止从生产者取值 end; end; Visitor模式和Iterator模式 回调实质是种简单Visitor模式说它简单是它只有Visitor没有Visited部分利用回调很难做到:消费者要生产者才给如果消费者不问不要生产者就不答不给这是由visitor模式特性所决定:回调使用者把Callback扔到遍历算法里面然后运行算法同时祈祷并等候算法完成(PushandWait)使用者完全失去了控制权只能等待算法整个完成或者中止才能重新拿到控制权而尽管使用Iterator模式能很容易做到这点(Iterator本质上属于问答模式或者说消费者/生产者模式Iterator使用方法本身就是Lazy问答遍历算法停在那里恭候Iterator使用者调遣)但是如果放弃回调方式却又无法复用消费者了要想同时做到既要复用“控制”又要复用“行为”这几乎是不可能(当然如果是仅仅想实现“消费者要生产者才给”那是可以不过难度比回调大而且并不能通用各种数据结构)visitor模式和Iterator模式特性恰恰是完全相反: *Iterator是种主动模型Pull模型AskandGetIterator听候用户调遣 *Vistor是种被动模型Push模型Plugin/callback模型PushandPrayandWaitVisitor听候算法调遣 //利用Iterator模式实现按消费者需要取值:简单、链表只要保存当前步骤(索引 //或者当前指针)和环境(内部数据集)结构返回给用户就可以了 //用户每次iterator.nextiterator就把索引或指针向后移动下如果是内部数据复杂Tree, //Graph结构就相当复杂了比如是遍历棵树而且这棵树Node里面没有Parent引用那么Iterator //必须自己维护个栈把前面所有ParentNode都保存起来 type TProducer= private i:eger; public Current:Integer; functionMoveNext:boolean; end; procedureTProducer.MoveNext; var i:eger; begin Result:=False; i<=100then begin imod5=0thenCurrent:=i; i:=i+1; Result:=True; end; end; procedureMainConsumer; begin withTProducer.Createdo try whileMoveNextdo begin Consumer(Current); .... //随时可以停止从生产者取值 end; [Page] finally Free; end; end; 这时候有聪明人就将目光转向了非对称式(asymmetric-coroutine)协程不难看出这里面Iterator就是Coroutine里面生产者(数据提供者所以有时我们也称的为Generator) 旦用户(消费者Consumer角色)了iterator.next(coroutine.Resume)Iterator就继续向下执行步然后把当前遇到内部数据Node放到个消费者用户能够看到公用缓冲区(比如直接放到消费者线程栈里面局部变量)里面然后自己就停下来(coroutine.Yield)然后消费者用户就从缓冲区里面获得了那个Node这样Iterator就可以自顾自地进行递归运算不需要自己管理堆栈上下文而是协程机制帮助它分配和管理运行栈从而实现将“控制”和“行为”彻底解藕请看如下C#: using.Collections.Generic; publicMyProducer:IEnumerable<> { //iteratorblock实现枚举元素控制 publicIEnumerator<>GetEnumerator { for(i=0;i<elements.Length;i) yieldelements[i]; } ... } foreach(iteminMyProducer) { //实现终端上打印元素行为 Console.WriteLine(item); } 在这段代码执行过程中foreach循环体和GetEnumerator体实际上是在同个线程中交替执行这是种介于线程和顺序执行的间协同执行模式的所以称的为协同(Coroutine)是同时执行多个代码块的间调度是由逻辑隐式协同完成就协同执行而言从功能上可以分为行为、控制两部分控制又可进步细分为控制逻辑和控制状态行为对应着如何处理目标对象如上述代码中:行为就是将目标对象打印到终端;控制则是如何遍历这个elements可进步细分为控制逻辑(顺序遍历)和控制状态(当前遍历到哪个元素)其中心思想在于通过Coroutine控制机制和Yield将其行为和控制彻底分离以此来进步降低代码耦合度增强通用性提高代码复用率 0
相关文章读者评论发表评论 |