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

原帖:http://blog.csdn.net/soloist/article/details/329381程序员

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

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

    Lua语言实现的协程是一种非对称式(asymmetric)协程,或称半对称式(semi-symmetric)协程,又或干脆就叫半协程(semi-coroutine)。这种协程机制之因此被称为非对称的,是由于它提供了两种传递程序控制权的操做:一种是(重)调用协程(经过coroutine.resume);另外一种是挂起协程并将程序控制权返回给协程的调用者(经过coroutine.yield)。一个非对称协程能够看作是从属于它的调用者的,两者的关系很是相似于例程(routine)与其调用者之间的关系。既然有非对称式协程,固然也就有对称式(symmetric)协程了,它的特色是只有一种传递程序控制权的操做,即将控制权直接传递给指定的协程。曾经有这么一种说法,对称式和非对称式协程机制的能力并不等价,但事实上很容易根据前者来实现后者。接下来咱们就用代码来证实这个事实。函数

--对称式协程库coro.luaoop

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

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

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

-- 把控制权及指定的数据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
操作系统

若是暂时还弄不懂上面的程序,不要紧,看看如何使用这个库后再回头分析。下面是使用示例:.net

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提供的非对称式协程不但具备与对称式协程同样强大的能力,并且还能避免程序员滥用机制写出结构混乱的程序。