并发是现实世界的本质特征,而聪明的计算机科学家用来模拟并发的技术手段便是多任务机制。大致上有这么两种多任务技术,一种是抢占式多任务(preemptive multitasking),它让操作系统来决定何时运行哪个任务。第二种就是协作式多任务(cooperative multitasking),它把决定权交给任务,让它们在自己觉得合适的时候自愿放弃运行。这两种多任务方式各有优缺点,前者固有的同步问题使得程序常常有不可预知的行为,而后者则要求任务具备相当的自律精神。

协程(coroutine)技术是一种程序控制机制,早在上世纪60年代就已提出,用它能够非常方便地实现协作式多任务。在主流的程序语言(如C++、Java、Pascal等)里我们非常少能看到协程的身影,可是如今不少动态脚本语言(Python、Perl)却都提供了协程或与之相似的机制,当中最突出的便是Lua。

Lua语言实现的协程是一种非对称式(asymmetric)协程,或称半对称式(semi-symmetric)协程,又或干脆就叫半协程(semi-coroutine)。这种协程机制之所以被称为非对称的,是由于它提供了两种传递程序控制权的操作:一种是(重)调用协程(通过coroutine.resume);还有一种是挂起协程并将程序控制权返回给协程的调用者(通过coroutine.yield)。一个非对称协程能够看做是从属于它的调用者的,二者的关系非常相似于例程(routine)与其调用者之间的关系。既然有非对称式协程,当然也就有对称式(symmetric)协程了,它的特点是仅仅有一种传递程序控制权的操作,即将控制权直接传递给指定的协程。以前有这么一种说法,对称式和非对称式协程机制的能力并不等价,但其实非常easy依据前者来实现后者。接下来我们就用代码来证明这个事实。

--对称式协程库coro.lua

--代码摘自论文"Coroutines in Lua"
--www.inf.puc-rio.br/~roberto/docs/corosblp.pdf

coro = {}
--coro.main用来标识程序的主函数
coro.main = function() end
-- coro.current变量用来标识拥有控制权的协程,
-- 也即正在运行的当前协程
coro.current = coro.main

-- 创建一个新的协程
function coro.create(f)
   return coroutine.wrap(function(val)
                            return nil,f(val)
                         end)
end

-- 把控制权及指定的数据val传给协程k
function coro.transfer(k,val)
   if coro.current ~= coro.main then
      return coroutine.yield(k,val)
   else
      -- 控制权分派循环
      while k do
         coro.current = k
         if k == coro.main then
            return val
         end
         k,val = k(val)
      end
      error("coroutine ended without transfering control...")
   end
end

假设临时还弄不懂上面的程序,没关系,看看怎样使用这个库后再回头分析。以下是使用演示样例:

require("coro.lua")

function foo1(n)
   print("1: foo1 received value "..n)
   n = coro.transfer(foo2,n + 10)
   print("2: foo1 received value "..n)
   n = coro.transfer(coro.main,n + 10)
   print("3: foo1 received value "..n)
   coro.transfer(coro.main,n + 10)
end

function foo2(n)
   print("1: foo2 received value "..n)
   n = coro.transfer(coro.main,n + 10)
   print("2: foo2 received value "..n)
   coro.transfer(foo1,n + 10)
end

function main()
   foo1 = coro.create(foo1)
   foo2 = coro.create(foo2)
   local n = coro.transfer(foo1,0)
   print("1: main received value "..n)
   n = coro.transfer(foo2,n + 10)
   print("2: main received value "..n)
   n = coro.transfer(foo1,n + 10)
   print("3: main received value "..n)
end

--把main设为主函数(协程)
coro.main = main
--将coro.main设为当前协程
coro.current = coro.main
--開始运行主函数(协程)
coro.main()

上面的演示样例定义了一个名为main的主函数,整个程序由它而始,也因它而终。为什么须要一个这种主函数呢?上面说了,程序控制权能够在对称式协程之间自由地直接传递,它们之间无所谓谁从属于谁的问题,都处于同一个层级,可是应用程序必须有一个開始点,所以我们定义一个主函数,让它点燃程序运行的导火线。虽说各个协程都是平等的,但做为程序运行原动力的主函数仍然享有特殊的地位(这个世上哪有绝对的平等!),为此我们的库专门用了一个coro.main变量来保存主函数,并且在它运行之前要将它设为当前协程(尽管上面的main实际仅仅是一个普通函数而非一个真正的协程,但这并无太大的关系,以后主函数也被称为主协程)。演示样例运行的结果是:

1: foo1 received value 0
1: foo2 received value 10
1: main received value 20
2: foo2 received value 30
2: foo1 received value 40
2: main received value 50
3: foo1 received value 60
3: main received value 70

协程的运行序列是:main->foo1->foo2->main->foo2->foo1->main->foo1->main。

coro.transfer(k,val)函数中k是将要接收程序控制权的协程,而val是传递给k的数据。假设当前协程不是主协程,tansfer(k,val)就简单地利用coroutine.yield(k,val)将当前协程挂起并传回两项数据,即程序控制权的下一站和传递给它的数据;否则进入一个控制权分派(dispatch)循环,该循环(重)启动(resume)k协程,等待它运行到挂起(suspend),并依据此时协程传回的数据来决定下一个要(重)启动的协程。从应用演示样例来看,协程与协程之间似乎是用transfer直接传递控制权的,但实际上这个传递还是通过了主协程。每个在主协程里被调用(比較coro.current和coro.main是否同样就可以推断出)的transfer都相当于一个协程管理器,它不断地(重)启动一个协程,将控制权交出去,然后等那个协程挂起时又将控制权收回,然后再(重)启动下一个协程...,这个动作不会停止,除非<1>将(重)启动的协程是主协程;<2>某个协程没有提供控制权的下一个目的地。非常显然,每一轮分派循环開始时都由主协程把握控制权,在循环过程中假设控制权的下一站又是主协程的话就意味着这个当初把控制权交出去的主协程transfer操作应该结束了,所以函数直接返回val从而结束这轮循环。对于情况<2>,由于coro.create(f)创建的协程的体函数(body function)实际是function(val) return nil,f(val) end,所以当函数f的最后一条指令不是transfer时,这个协程终将运行完成并把nil和函数f的返回值一起返回。假设k是这种协程,transfer运行完k,val = k(val)语句后k值就成了nil,这被视为一个错误,由于程序此时没法确定下一个应该(重)启动的协程究竟是谁。所以在对称式模型下,每个协程(当然主协程出外)最后都必须显式地将控制权传递给其它的协程。依据以上分析,应用演示样例的控制权的分派应为:

第一轮分派: main->foo1->main->foo2->main->main(结束)
第二轮分派: main->foo2->main->foo1->main->main(结束)
第三轮分派: main->foo1->main->main(结束)

由于能够直接指定控制权传递的目标,对称式协程机制拥有极大的自由,但得到这种自由的代价却是牺牲程序结构。假设程序略微复杂一点,那么即使是非常有经验的程序猿也非常难对程序流程有全面而清晰的把握。这非常相似goto语句,它能让程序跳转到不论什么想去的地方,但人们却非常难理解充斥着goto的程序。非对称式协程具有良好的层次化结构关系,(重)启动这些协程与调用一个函数非常相似:被(重)启动的协程得到控制权開始运行,然后挂起(或结束)并将控制权返回给协程调用者,这与计算机先哲们倡导的结构化编程风格全然一致。

综上所述,Lua提供的非对称式协程不但具有与对称式协程一样强大的能力,并且还能避免程序猿滥用机制写出结构混乱的程序。

Lua的多任务机制——协程(coroutine)的更多相关文章

  1. (zt)Lua的多任务机制——协程(coroutine)

    原帖:http://blog.csdn.net/soloist/article/details/329381 并发是现实世界的本质特征,而聪明的计算机科学家用来模拟并发的技术手段便是多任务机制.大致上 ...

  2. Lua 协程coroutine

    协程和一般多线程的区别是,一般多线程由系统决定该哪个线程执行,是抢占式的,而协程是由每个线程自己决定自己什么时候不执行,并把执行权主动交给下一个线程. 协程是用户空间线程,操作系统其存在一无所知,所以 ...

  3. Lua的协程(coroutine)

    -------------------------------------------------------------------------------- -- 不携带参数 ---------- ...

  4. qemu核心机制分析-协程coroutine

    关于协程coroutine前面的文章已经介绍过了,本文总结对qemu中coroutine机制的分析,qemu 协程coroutine基于:setcontext函数族以及函数间跳转函数siglongjm ...

  5. 协程coroutine

    协程(coroutine)顾名思义就是“协作的例程”(co-operative routines).跟具有操作系统概念的线程不一样,协程是在用户空间利用程序语言的语法语义就能实现逻辑上类似多任务的编程 ...

  6. Unity协程(Coroutine)管理类——TaskManager工具分享

    博客分类: Unity3D插件学习,工具分享 源码分析   Unity协程(Coroutine)管理类——TaskManager工具分享 By D.S.Qiu 尊重他人的劳动,支持原创,转载请注明出处 ...

  7. Python并发编程协程(Coroutine)之Gevent

    Gevent官网文档地址:http://www.gevent.org/contents.html 基本概念 我们通常所说的协程Coroutine其实是corporate routine的缩写,直接翻译 ...

  8. Lua的函数调用和协程中,栈的变化情况

    Lua的函数调用和协程中,栈的变化情况 1. lua_call / lua_pcall   对于这两个函数,对栈底是没有影响的--调用的时候,参数会被从栈中移除,当函数返 回的时候,其返回值会从函数处 ...

  9. 并发编程协程(Coroutine)之Gevent

    并发编程协程之Gevent Gevent官网文档地址:http://www.gevent.org/contents.html 基本概念 我们通常所说的协程Coroutine其实是corporate r ...

随机推荐

  1. ios至于理解锚

    锚点ios出现在少数地方,多数用在动画. 今天看了一部电影,以上所有关于锚,两年前锚这个概念看cocos2d当被接触的基本概念,当时我没怎么看,今天看了,刚刚好学习. 阅读blog,它是关于锚,像: ...

  2. 系列二VS项目软件配置工具介绍

    原文:系列二VS项目软件配置工具介绍 Svn和VisualSvn介绍 在使用TortoiseSvn(SVN客户端)+ AnkhSvn(VS2008插件) +VisualSvn Server(版本控制服 ...

  3. HDU 4896 Minimal Spanning Tree(矩阵高速功率)

    意甲冠军: 给你一幅这样子生成的图,求最小生成树的边权和. 思路:对于i >= 6的点连回去的5条边,打表知907^53 mod 2333333 = 1,所以x的循环节长度为54,所以9个点为一 ...

  4. 使用更清晰DebugLog开发和调试工具

    在开发和应用的开发和调试过程中难免会发现故障的过程中.我相信很多做iOS开发程序员Xcode的debug调试功能大加关注. 但在这样做Android开发过程中,却不那么方便,虽然IDE也提供了debu ...

  5. UVA10375 Choose and divide 质因数分解

    质因数分解: Choose and divide Time Limit: 3000MS   Memory Limit: Unknown   64bit IO Format: %lld & %l ...

  6. k8s with flanneld

    三台机器 kmaster 192.168.1.201 kslave202 192.168.1.202 kslave203 192.168.1.203 安装好k8s 1. 在Node机器上安装flann ...

  7. ImageView建立selector在录音中遇到的小问题及解决方案

    随着两张照片做了一个selector,采用ImageView的src要么background采用selector当点击,总不会出现点击效果,这就是为什么?经过一番折腾,后来发现"揭秘&quo ...

  8. MapReduce架构和算法(2)

    一个.combiner计划 每map它可能会产生大量的输出,combiner的作用是map输出端先做合并.reducer的数据量. combiner最基本是实现本地key的归并,combiner具有类 ...

  9. CentOS7 安装NFS SSH免密码登陆

    配置5台虚拟机 ip为192.168.1.160 - 164,使用160作为共享服务器 使用yum安装nfs 以及rpcbind,有很多文章介绍,这里不再赘述 一.启动服务 1.启动rpcbind s ...

  10. JAVA学习第五十九课 — 网络编程概述

    网络模型 OSI(Open System Interconnection)开放系统互连:參考模型 TCP/IP 网络通讯要素 IP地址 port号 传输协议 网络參考模型 七层OSI模型的基本概念要了 ...