GameDesign 板


LINE

图文版 http://goo.gl/01f0e 很久以前就想要写一篇介绍 coroutine 的文章。Coroutine 对於游戏程式来说 是个非常有用的语言功能,不过学校课程几乎不会介绍这个东西。最近藉由实作 上的经验,整理出几篇心得,请各位慢慢品尝。 * * * * * * Coroutine 一般翻译作「协程」,对一般 programmer 来说可能有些陌生,但其 实这个想法早在 1963 年就被提出来了。近年来因为 multi-threading 的普及, 主流语言鲜少提供 coroutine 的功能,但即使 coroutine 与 thread 的概念相 似,却有许多截然不同的特性,不少场合使用 coroutine 既能优雅地解决问题, 又能避免 multi-threading 的 race condition。本系列文章将会介绍 coroutine 的基本概念及应用场合,让原本复杂的流程变得简洁易懂。 Lua 是支援 coroutine 的语言之一,同时也具备简单易学的特性,以下将会使用 Lua 程式码作为 coroutine 的示范。对 Lua 不熟悉的读者可以参考 Lua Tutorial Dictionary 上的教学,实际学起来是很快的。 [Lua Tutorial Dictionary]: http://lua-users.org/wiki/TutorialDirectory Coroutine 基本概念 Coroutine 可以视为「可以中断及继续执行的函式呼叫」。在一般的程式语言中, 呼叫某个函式时,该函式一定是从头开始执行: function foo(n) print("hello") print("world") print("n^2 = "..(n*n)) end foo(10) -- 不间断地印出三行字 然而 coroutine 允许函式执行到一半就中断(yield),中断时内部状态会被保 留下来,呼叫端可以随时在之後恢复(resume)这个 coroutine。 function bar(n) print("hello") print("world") coroutine.yield() -- 在这里中断 coroutine print("n^2 = "..(n*n)) end local co = coroutine.create(bar) -- co 是保留 coroutine 内部状态的变数 coroutine.resume(co, 10) -- 只印出前两行 hello world print("this is the third line.") -- 中断後主程式可以做其它事 coroutine.resume(co) -- 印出最後一行 n^2 = 100 上述的范例即为 coroutine 的呼叫方法: 1. 呼叫端并非直接呼叫函式,而是使用 coroutine.create 来产生一个 coroutine object。这个 object 将会储存 coroutine 的执行状态, 包括区域变数的内容及中断点的位置。 2. 产生 coroutine object 的时候并不会呼叫函式,而是在第一次使用 coroutine.resume 的时候,才会从函式的开头开始执行。 3. 在函式中使用 coroutine.yield 时,将会中断函式执行并保留中断时 的状态,控制权随即转移到呼叫端。 4. 呼叫端再次执行 coroutine.resume 的时候,流程将会回到 coroutine 上次中断之处,继续往下执行到 yield 或函式结束。 任意中断的特性 Coroutine 中的两项操作:yield 及 resume,在 Lua 中只是基本的函式呼叫。 你可以使用任意数量的 yield 来中断 coroutine,当然也可以放在回圈之类的 控制结构当中。 function fibb() local a = 1 local b = 0 while true do print(a) coroutine.yield() local tmp = a a = a + b b = tmp end end local co = coroutine.create(fibb) for i=1,20 do -- 印出费氏数列前20项 coroutine.resume(co) end 当然,在 coroutine 中可以呼叫其它函式,而 yield 也能出现在更深层的函式 当中。我们会在下一篇文章中介绍这个应用。 Coroutine 的资料传递 在上面费氏数列的例子中,我们都是把数字印出来後才呼叫 coroutine.yield, 这显然弹性不够--呼叫端可能想把数字写到档案、或是塞进阵列中。我们能直 接把数值在 yield 的同时直接传回到主程式吗?答案是肯定的,而且方法非常 简单:直接当作参数丢给 coroutine.yield。 function fibb() local a = 1 local b = 0 while true do coroutine.yield(a) local tmp = a a = a + b b = tmp end end local co = coroutine.create(fibb) local array = {} -- 建立空阵列 local state -- 表示 coroutine 的状态 for i=1,20 do -- 把费氏数列前20项填进阵列中 state, array[i] = coroutine.resume(co) end coroutine.resume 的第一个回传值皆为 true 或 false,表示该 coroutine 是否 正确执行。而第二个以後的回传值则是 coroutine 在中断时传进 coroutine.yield 的参数,因此在第 15 行我们需要用两个变数来接 coroutine 的回传值。 反过来说,主程式也可以在呼叫 coroutine.resume 的时候添加参数,这些参数会 被传递到 coroutine 当中,作为 coroutine.yield 的回传值。 function foo(n) print("n = " .. n) while true do print("received " .. coroutine.yield()) end end local co = coroutine.create(foo) coroutine.resume(co, 10) -- 印出 n = 10 coroutine.resume(co, 20) -- 印出 received 20 coroutine.resume(co, 30) -- 印出 received 30 coroutine.resume(co, 40) -- 印出 received 40 第一次呼叫 coroutine.resume 时,添加的参数会被当作是函式的参数,也就是 n。 第二次以後的 resume 其参数则会被当作 yield 的回传值送进 coroutine 之中。 籍由资料传递的功能,我们可以利用 coroutine 轻松实作出 iterator pattern。 我会在下一篇文章介绍这项应用。 Coroutine 与 Generator 严格说来,Lua 所提供的 coroutine 应该要称之为 generator。根据 wikipedia 的说法,coroutine 在进行 yield 操作时需指定另一个 coroutine 作为参数, 因此多个 coroutine 之间可以随意跳来跳去。而在 Lua 的 coroutine 中,yield 必然回到上次呼叫 resume 的地方。 这是 wikipedia 上的例子,注意 yield 时要同时指定流程跳跃的目地。 var q := new queue coroutine produce loop while q is not full create some new items add the items to q yield to consume coroutine consume loop while q is not empty remove some items from q use the items yield to produce 严格定义下的 coroutine 实际上可以用 generator 来达成,而许多语言似乎也 没有很精确地区分这两者。在接下来的文章中,我会继续用 coroutine 来称呼。 Coroutine 与 Thread 在系统层面上,coroutine 通常和 fiber 是一样的意思。Fiber 是一种特别的 user thread,如同一般的 thread 那样具有自己的 call stack 与 program counter,但具备了以下的特性: * Thread 不需自己定义中断点(也就是呼叫 yield 的地方),而是让 OS 或 thread library 来决定是否进行 context switch,通常由执行时间来判断。 然而 fiber 需要自行定义中断点,context switch 只会发生在明确呼叫 yield 的地方。 * 因为 context switch 是由使用者自行控制,因此 fiber 通常不需要 mutex 之类的东西来避免 race condition。 * Kernel thread 会进入 OS 的排程中,在多核心的 CPU 上可能会使用不同的 核心同时执行许多 kernel thread。但 fiber 属於特别种类的 user thread, 它无法利用多核心进行平行处理。 * * * * * * 下一篇文章中,我将以 iterator 作为例子,介绍简单的 coroutine 应用方式。 --



※ 发信站: 批踢踢实业坊(ptt.cc)
◆ From: 202.39.238.241
1F:推 s3748679:支持~ 加油~ -w-" 06/28 23:24
2F:推 adxis:GJ! 06/29 00:32
3F:推 gpmm:push 06/29 00:57
※ 编辑: littleshan 来自: 202.39.238.241 (07/08 17:54)
4F:推 wangm4a1:push 07/16 22:58
5F:推 SamMa: 感谢分享 受益良多 08/12 14:17







like.gif 您可能会有兴趣的文章
icon.png[问题/行为] 猫晚上进房间会不会有憋尿问题
icon.pngRe: [闲聊] 选了错误的女孩成为魔法少女 XDDDDDDDDDD
icon.png[正妹] 瑞典 一张
icon.png[心得] EMS高领长版毛衣.墨小楼MC1002
icon.png[分享] 丹龙隔热纸GE55+33+22
icon.png[问题] 清洗洗衣机
icon.png[寻物] 窗台下的空间
icon.png[闲聊] 双极の女神1 木魔爵
icon.png[售车] 新竹 1997 march 1297cc 白色 四门
icon.png[讨论] 能从照片感受到摄影者心情吗
icon.png[狂贺] 贺贺贺贺 贺!岛村卯月!总选举NO.1
icon.png[难过] 羡慕白皮肤的女生
icon.png阅读文章
icon.png[黑特]
icon.png[问题] SBK S1安装於安全帽位置
icon.png[分享] 旧woo100绝版开箱!!
icon.pngRe: [无言] 关於小包卫生纸
icon.png[开箱] E5-2683V3 RX480Strix 快睿C1 简单测试
icon.png[心得] 苍の海贼龙 地狱 执行者16PT
icon.png[售车] 1999年Virage iO 1.8EXi
icon.png[心得] 挑战33 LV10 狮子座pt solo
icon.png[闲聊] 手把手教你不被桶之新手主购教学
icon.png[分享] Civic Type R 量产版官方照无预警流出
icon.png[售车] Golf 4 2.0 银色 自排
icon.png[出售] Graco提篮汽座(有底座)2000元诚可议
icon.png[问题] 请问补牙材质掉了还能再补吗?(台中半年内
icon.png[问题] 44th 单曲 生写竟然都给重复的啊啊!
icon.png[心得] 华南红卡/icash 核卡
icon.png[问题] 拔牙矫正这样正常吗
icon.png[赠送] 老莫高业 初业 102年版
icon.png[情报] 三大行动支付 本季掀战火
icon.png[宝宝] 博客来Amos水蜡笔5/1特价五折
icon.pngRe: [心得] 新鲜人一些面试分享
icon.png[心得] 苍の海贼龙 地狱 麒麟25PT
icon.pngRe: [闲聊] (君の名は。雷慎入) 君名二创漫画翻译
icon.pngRe: [闲聊] OGN中场影片:失踪人口局 (英文字幕)
icon.png[问题] 台湾大哥大4G讯号差
icon.png[出售] [全国]全新千寻侘草LED灯, 水草

请输入看板名称,例如:WOW站内搜寻

TOP