用户操作
[即时聊天] [发私信] [加为好友]
邓际锋ID:soloist
84704次访问,排名1139好友1人,关注者2
soloist的文章
原创 39 篇
翻译 0 篇
转载 0 篇
评论 189 篇
soloist的公告
欢迎吹毛求疵,感谢您对任何错误的指正,包括技术的、语法的、用词的、标点的、典故的、引用资料的……
最近评论
qingbai:lua绝对是个好东西。但国内除了java就是.net,其他东西没法活。因为程序员得工作,得吃饭。国内有哪家公司用lua?唉没办法呀。国外是一片繁荣,“百家争鸣”,国内是“青一色”的java和.net!无奈!
zhangyilan:尽管没有在实际代码的编写中碰到这个问题,不过也先学习一下,免得出现问题了搞出清楚情况。
ddrmsdos:这篇文章写的太好了,分析的非常仔细,以前常常碰到这类问题,终于解了我多年的心头之患......
ollydbg23:楼主的这篇文章写的非常好啊!
我看了以后,还是挺有收获感的,多谢多谢!
我也是对汇编,c++的比较感兴趣,有空可以交流一下!
w2001:写得很好
文章分类
收藏
    相册
    好博链接
    C++罗浮宫
    cpper
    fixopen
    fmddlmyy
    neoragex2002
    whinah
    云风
    梦想风暴
    沉思者
    许式伟
    负暄琐话
    辣子鸡丁
    存档
    软件项目交易
    订阅我的博客
    XML聚合  FeedSky
    订阅到鲜果
    订阅到Google
    订阅到抓虾
    订阅到BlogLines
    订阅到Yahoo
    订阅到GouGou
    订阅到飞鸽
    订阅到Rojo
    订阅到newsgator
    订阅到netvibes

    原创 Lua的多任务机制——协程(coroutine)收藏

    新一篇: 在Win2000注册表中修改中文输入法切换快捷键 | 旧一篇: Lua的function、closure和upvalue

        并发是现实世界的本质特征,而聪明的计算机科学家用来模拟并发的技术手段便是多任务机制。大致上有这么两种多任务技术,一种是抢占式多任务(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.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提供的非对称式协程不但具有与对称式协程一样强大的能力,而且还能避免程序员滥用机制写出结构混乱的程序。

    发表于 @ 2005年03月24日 22:37:00|评论(loading...)|编辑

    新一篇: 在Win2000注册表中修改中文输入法切换快捷键 | 旧一篇: Lua的function、closure和upvalue

    评论

    #buxiu 发表于2005-04-14 15:42:00  IP: 218.94.57.*
    想交个朋友,msn:eyangzhou@hotmail.com;QQ:5323282
    #刘未鹏 发表于2005-11-22 06:56:00  IP: 222.94.28.*
    问一个问题:协程和线程有何区别?我似乎看不出什么本质的区别,呵呵;-)
    另外,上面那段演示程序实在是看得我不想看下去了^_^让我想起Scheme/Lisp里面的Yin/Yang程序。
    #soloist 发表于2005-11-22 21:35:00  IP: 218.78.226.*
    是啊,那段代码的确有点晦涩,我看了好长时间才明白,所以弄懂了之后赶紧记了下来,否则将来一定又要从头再想一遍。

    协程和线程的区别嘛,嘿嘿,我也说不上。感觉协程就应该是协作起来的线程吧。

    你说的“Scheme/Lisp里面的Yin/Yang程序”我有所耳闻,不过却从来不知道是个啥东东。可否介绍一下?
    #刘未鹏 发表于2005-11-24 07:04:00  IP: 222.94.28.*
    我还没时间了解Lua,只是前一阵子粗浅的看了一下语言特性,这个协程更是一无所知,不过从你说的来看感觉顶多也就是个语言级别的线程机制罢了。不知道为何要搞得似乎这么高深,等以后了解了再来拍砖。

    Yin/Yang程序王咏刚的blog上有。
    是一个典型的完弄语言技巧的例子,quite confusing though.
    #soloist 发表于2005-11-25 20:18:00  IP: 218.78.227.*
    对,它就是个语言内置的线程。

    可能是我的题目取得不好,这篇文章其实主要不是想介绍Lua的协程机制,而是比较对称式协程和非对称式协程。

    文章中用Lua的非对称式协程完全模拟了一个对称式协程机制,从而反驳了一个流传很广的误解,即非对称式功能比对称式要弱。

    并且,就我的感觉来说,类似Lua这种非对称式协程用起来非常自然,而由文章中的代码则可以很明显看出对称式协程的执行动作就没那么直观了。
    #soloist 发表于2005-11-27 19:46:00  IP: 218.78.227.*
    嗯,如果从操作系统调度的角度去说,Lua的协程与线程确实不是一回事。
    #ishou 发表于2005-11-27 18:39:00  IP: 218.186.251.130, 202.156.6.*
    这里的 “协程” 跟我们平常所说的 “线程” 好象 完全是两码事,所有的 “协程” 都是在主 线程中 运行!只是这里设计一种技巧程序结构,程序可以在数个 函数(“协程”) 之间交递运行。如果在某个协程里执行一行代码花费相当时间(尤其当调用外来函数),其他协程就会处于明显的“睡眠”状态。
    脚本语言里要实现真正的 “线程”,不是易事。听说 Java里的 “线程”也不可靠。 大家非常熟悉的VB,没有直接提供 “线程”功能。
    #fixopen 发表于2006-01-06 12:07:00  IP:
    也不能说协程就是线程,协程可以实现线程。当然,反过来也一样,就看我们的立足点了。

    协程是这样推导出来的:
    首先,我么需要一个例程的概念,也就是函数或者过程。然后,我们会有调用和被调用这个关系。
    由于结构化思想的流行,慢慢的稳定在一个例程就一个入口一个出口,调用者和被调用者是层次嵌套的关系。虽然,这个概念非常强大了,但是,这个想法明明有些概念不好表达,最好的例子就是:

    一个例程,用来生成一个无穷序列,另一个例程,处理这个序列中的每一项。如果用传统的调用返回机制,这个没有办法。这时候,我们就可以设想让这两个例程协作:生成例程没生成一个,就转移到处理例程中去,处理完了以后,再转移到生成例程。这就是协程。从中可以看出,协程是那种可以多次进入多次退出的例程。

    Kunth曾经说过,普通例程只是协程的特例。
    #soloist 发表于2006-01-06 15:37:00  IP: 218.78.226.*
    to fixopen:

    --- 协程是那种可以多次进入多次退出的例程。
    good,这个概括简单明了。再补充上下面一句就更好了:每次挂起时保留与之相关的执行上下文(execution contex),下次重入将自动恢复。

    --- 普通例程只是协程的特例。
    very 同意。
    发表评论  


    当前用户设置只有注册用户才能发表评论。如果你没有登录,请点击登录
    Csdn Blog version 3.1a
    Copyright © soloist