前言
- 为什么会有协程?背景
- 什么是协程?认识
- 怎么使用协程?使用
Tip1:扩展阅读必读
Tip2:明白了yield和resume之间的切换和参数传递,也就明白了协程。
Tip3:必读资料
http://manual.luaer.cn/2.11.html
http://pgl.yoyo.org/luai/i/2.11+Coroutines
为什么会有协程?
在编程时,难免会遇到一些带有阻塞的流程,例如最简单的led闪烁
例1: main(){ while(1):{ led =1; delay_ms(1000); led = 0; delay_ms(1000); } }
面对这样的阻塞的状况,我们可以自己编写状态机,绕过这个阻塞。
//定时器,50ms void Timer0() { t_cnt ++; if(t_cnt>2000) t_cnt = 0; } main() { while(1) { switch(step) { case 0: if(t_cnt > 20) { led = 1; t_cnt = 0; step ++; } break; case 1: if(t_cnt > 20) { led = 0; t_cnt = 0; step --; } break; default: step = 0; break; }}}
这只是一个非阻塞的状态机模式的思路的展示,也可以使用if..else..编写。
在lua中编写非阻塞的流程控制也要像c一样自己搞状态机吗?不,lua中有现成的非阻塞编程框架:协程。
在这里引用一篇博文下,silverys的评论。
6 楼 silverys 2014-11-19其实协程并没有那么复杂,网上很多地方都说是多线程什么的,真是误人子弟。。
在传统实时游戏中,在update中要延迟执行一些代码,或者满足一定条件后执行一些代码。需要在update添加一个计时器,用当前时间来减去前面记录的时间来判断执行。当这种情况越来越多的时候,会添加很多变量和代码,代码就越来越乱。。
实时游戏写多了就知道。。这时候一般会抽象一个框架出来处理这个问题。
而协程可以说是unity的一个框架,为解决这个问题而设计的一个设计模式。。套着这个概念来写,让这种代码非常整洁易维护。。和C#的多线程是两码事。。
怎么使用协程?
协程就是使用resume唤醒coroutine,并传入参数,使用yield暂停coroutine(resume立刻return yield的参数)
coroutine基本的语法和的几个函数要了解清楚,熟记于心,通过掌握这几个函数,会对协程的机制有更加清晰、细致的认识。
coroutine.create()
功能:新建一个协同程序
参数:唯一的参数是这个协程的主函数。
返回值:此协程的句柄(thread类型)
说明:创建后的协同程序状态为suspended挂起状态(另,running运行状态,dead死亡状态),并没有运行。
原文:
You create a coroutine with a call to
coroutine.create
. Its sole argument is a function that is the main function of the coroutine. Thecreate
function only creates a new coroutine and returns a handle to it (an object of type thread); it does not start the coroutine execution.
#例1:create的新协程:main function co = coroutine.create(function() print("hello,world") print("Goodbye,honey") end)
coroutine.resume()
功能:启动一个suspended挂起状态的协同程序
参数:协程的句柄,其他的参数会传递给协程
返回值:true(代表成功调用)+value(main function有返回值 or yield的参数 or nothing)/false+error message
说明:resume一个协程时,有两种方式结束(注意:不是暂停)其运行:
1、create中的function运行到最后(return或者最后一条指令)
2、运行中出现不可预料的错误
原文:
When you first call
coroutine.resume
, passing as its first argument a thread returned bycoroutine.create
, the coroutine starts its execution, at the first line of its main function. Extra arguments passed tocoroutine.resume
are passed on to the coroutine main function. After the coroutine starts running, it runs until it terminates or yields.A coroutine can terminate its execution in two ways: normally, when its main function returns (explicitly or implicitly, after the last instruction); and abnormally, if there is an unprotected error. In the first case,
coroutine.resume
returns true, plus any values returned by the coroutine main function. In case of errors,coroutine.resume
returns false plus an error message.
#例2:resume的参数传递 co = coroutine.create(function (a,b,c) print("co", a,b,c) end) coroutine.resume(co, 1, 2, 3) --> co 1 2 3
在coroutine.resume(co,1,2,3),传递co这个句柄,链接到了create内部的function(a,b,c),并且把参数1,2,3传递进去,接着print,就输出了 co 1 2 3
Q:什么时候协同程序会变成dead死亡状态呢?
A:当coroutine.create内的main function执行完毕,它就dead了。如果还想使用,就再次create,然后使用resume激活执行。
coroutine.yield()
功能:暂停协程
参数:参数就是给相应的resume的返回值value
返回值:true(代表成功调用)/false+error message
说明:执行到yield,会挂起当前协程,coroutine.resume立刻return true和yield的参数。不管这个yield是放在协程中,还是协程间接、直接调用的函数中。
原文:
A coroutine yields by calling
coroutine.yield
. When a coroutine yields, the correspondingcoroutine.resume
returns immediately, even if the yield happens inside nested function calls (that is, not in the main function, but in a function directly or indirectly called by the main function). In the case of a yield,coroutine.resume
also returns true, plus any values passed tocoroutine.yield
. The next time you resume the same coroutine, it continues its execution from the point where it yielded, with the call tocoroutine.yield
returning any extra arguments passed tocoroutine.resume
.
最后一句话的翻译:下次使用resume唤醒协程时,会从之前暂停的点继续执行,resume会把自己额外的参数(即除了协程的句柄的其他参数)传递给yield语句(可理解为用resume的额外参数直接替换yield语句)
#例2:yield和resume参数的传递
function foo (a) print("foo", a) return coroutine.yield(2*a) end co = coroutine.create(function (a,b) print("co-body", a, b) local r = foo(a+1) print("co-body", r) local r, s = coroutine.yield(a+b, a-b) print("co-body", r, s) return b, "end" end) print("main", coroutine.resume(co, 1, 10)) print("main", coroutine.resume(co, "r")) print("main", coroutine.resume(co, "x", "y")) print("main", coroutine.resume(co, "x", "y"))
运行结果:
co-body 1 10 foo 2 main true 4 co-body r main true 11 -9 co-body x y main true 10 end main false cannot resume dead coroutine
代码执行分析:
step1:执行第一个print,出现resume,切换进入create的协程function中,打印出 co-body 1 10
step2:执行function中第二句(定义一个局部变量r并赋值),进入foo,打印出 foo 2
step3:执行foo中的第二句,return时遇到yield(foo中的这个return并没有得到执行,因为要先执行yield再return),协程正常暂停,此时resume函数立刻return一个true和 coroutine.yield(2*a)的参数 2*a(=4)
step4:执行第一个print,打印出main true 4
step5:执行第二个print,程序从上次暂停的为止foo(a)中的return执行,且resume把参数(字符"r")传递给yield,则foo返回值为字符"r",赋值给local r
step6:第二个print,则打印出 co-body r
step7:执行 local r, s = coroutine.yield(a+b, a-b),协程正常暂停,此时resume函数立刻return 一个true和coroutine.yield(a+b,a-b)的参数a+b,a-b,打印出 main true 11 -9
step8:执行第三个print时,又遇到resume,从上次暂停的地方运行,resume把参数x,y传递给yield,则local r,s = x ,y,打印出 co-body x y
step9:因为协程是正常执行到return,正常结束,resume返回true和协程中return的值 10 end。则第三个print,则打印出 main true 10 end
coroutine.status()
查看coroutine的状态
注:coroutine的状态有三种:dead,suspended,running,具体什么时候有这样的状态请参考下面的程序
coroutine.wrap()
功能:创建一个协程
参数:main function
返回值:协程的句柄
说明:使用wrap创建的协程在resume时,返回值不包含true和false,当然false时也不会返回error message。这样可以让程序清晰,但不会报错,会影响调试。每次使用wrap返回的函数,都相当于隐式调用了resume,唤醒了协程的main function。
原文:
Like
coroutine.create
, thecoroutine.wrap
function also creates a coroutine, but instead of returning the coroutine itself, it returns a function that, when called, resumes the coroutine. Any arguments passed to this function go as extra arguments tocoroutine.resume
.coroutine.wrap
returns all the values returned bycoroutine.resume
, except the first one (the boolean error code). Unlikecoroutine.resume
,coroutine.wrap
does not catch errors; any error is propagated to the caller.
例子: local f = coroutine.wrap( function(a,b) print(a,b) local x,y=coroutine.yield("yield") print(x,y) return ('bingo') end) print(f(1,2)) print(f(8,9)) --> 1 2 yield 8 9 bingo
coroutine.running()
返回正在跑的coroutine,一个coroutine就是一个线程,当使用running的时候,就是返回一个coroutine的句柄
---------------------------------------------------------------------------------------------------------------------