lua协程笔记

简单的认识一下cortoutine

lua中有一个令初学者头疼的概念,协程。我们在tencent的xlua的代码中也可以看到他的身影。
今天我就准备好好整理一下这个东西。用更浅显易懂的说法让大家更快更好的理解。

首先从协程的名字上,我们就可以知道他是协助,协作的一种程序。虽然他的类型是线程。但是实际上他不是异步线程。它和主线程之间无缝切换。没有线程切换的开销。所以把他当做一个子程序理解更容易些。

我们先看一个简单的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
function myfunction(param)
print("====myfunction=====start")
print("myfunction 参数",param)
print("准备第第一次挂起子程序")
local result = coroutine.yield("first yield")
print("first yield从主程序得到的结果",result)
print("准备第二次挂起子程序")
local result2 = coroutine.yield("second yield")
print("second 从主程序得到的结果",result2)
print("子程序执行结束")
end

local cor = coroutine.create(myfunction)
print("start the coroutine")
local status,result = coroutine.resume(cor,"first resume")
if status == true then
print("协程没有结束,first resume 从子程序得到结果是 ",result)
else
print("协程已经结束")
return
end

print("准备第二次resume子程序")
status,result = coroutine.resume(cor,"second resume")
if status == true then
print("协程没有结束,second resume 从子程序得到结果是 ",result)
else
print("协程已经结束")
return
end
print("准备第三次resume子程序")
status,result = coroutine.resume(cor,"third resume")
if status == true then
print("协程没有结束,third resume 从子程序得到结果是 ",result)
else
print("协程已经结束")
return
end
print("准备第四次resume子程序")
status,result = coroutine.resume(cor,"forth resume")
if status == true then
print("协程没有结束,forth resume 从子程序得到结果是 ",result)
else
print("协程已经结束")
end

上述程序的执行结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
start the coroutine
====myfunction=====start
myfunction 参数 first resume
准备第第一次挂起子程序
协程没有结束,first resume 从子程序得到结果是 first yield
准备第二次resume子程序
first yield从主程序得到的结果 second resume
准备第二次挂起子程序
协程没有结束,second resume 从子程序得到结果是 second yield
准备第三次resume子程序
second 从主程序得到的结果 third resume
子程序执行结束
协程没有结束,third resume 从子程序得到结果是 nil
准备第四次resume子程序
协程已经结束

执行过程讲解

coroutine.create创建了一个函数myfunction的子程序。myfunction函数自然没有执行。此时主程序开始执行的时候,遇到resume唤醒了子程序。此时正在执行的主程序,将控制权交给了子程序。自己处于不执行状态。子程序执行到yield的时候自己又被挂起。于是控制权又交还了主程序。于是主程序又开始执行。然后又遇到了resume。控制权又给了子程序,如此往复。直到子程序彻底结束了。

这个流程很容易理解。于是有人就问,两个子程序之间看样子好像也可以出现传递函数啊?
答对了。是的。主程序执行resume唤醒子程序,第一个参数,是协程对象,不必说。第二个就是传递给子程序的参数。但是这个参数的传递位置比较特殊。

函数被调用的时候,一般有函数入口。都是在函数执行的时候收到参数。但是协程的子程序不同。resume子程序,子程序是在上次没执行完的地方继续开始执行。那么上次没执行完,就是执行yield的时候。比如:

1
local result = coroutine.yield("first yield")

此时执行赋值操作的右侧yield。继续执行的话,那么就要执行左侧。于是主程序resume的传递过来的参数就给了子程序result
同理子程序yield的参数,也是同样的方法传递给了主程序。

我将上面的步骤整理成流程图。
=w400

从上图我们能看到,其实主线程在一直在主程序和子程序之间来回切换。一直同步执行。没有发生异步。也就是一旦协程开始工作,主程序并没有工作。主程序开始工作,协程也挂起。实际应用中,比如http请求,我们发送了请求,请求没有回来之前可以让协程一直挂起。知道请求回来,在让协程工作。主程序挂起。

来点有难度的

这么说下来很多人会说好像不难。但是实际使用的时候又是另一回事。因为协程的变化太多。往往不想上面代码中那么容易区分主程序和子程序。
例如xlua中07_AsyncTest的例子。我们来分析一下它的代码。
在xlua中,协程最关键的类是util.async_to_sync函数
所以我们结合一个07的这个例子来看一下它是怎么工作的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
local function async_to_sync(async_func, callback_pos)
return function(...)
local _co = coroutine.running() or error ('this function must be run in coroutine')
local rets
local waiting = false
local function cb_func(...)
if waiting then
assert(coroutine.resume(_co, ...))
else
rets = {...}
end
end
local params = {...}
table.insert(params, callback_pos or (#params + 1), cb_func)
async_func(unpack(params))
if rets == nil then
waiting = true
rets = {coroutine.yield()}
end

return unpack(rets)
end
end

首先CS.MessageBox.ShowAlertBoxCS.MessageBox.ShowConfirmBox都使用了协程。他们按钮的回调,会调用上面的cb_func函数。如果不点击按钮,cb_func就不执行。

所以当我们弹框一个alertbox时候,上面函数会执行async_func用来绑定按钮回调。执行rets==nil的条件,coroutine.yield()回到主程序。等玩家点击了Button,然后在执行coroutine.resume(_co, ...)唤醒子程序,将结果返回给rets。
因此弹出框,子程序瞬间执行结束,然后返回主程序,主程序执行。等玩家点击按钮。唤醒子程序,然后将唤醒的结果返回给主程序。
就这么回事。

他的购买流程就简单多了,开始执行charge(10)的时候,直接执行了cb_func函数,直接将rets={...},上面传入的参数就是true,10。后面代码关于协程的一个没有执行。主程序将rets返回,local r1, r2 = recharge(10)。因此r1,r2才有值。

小惊喜

和协程相关的就说这么多吧。另外举一反三一下。在python中,尤其是python3中的协程和lua的功能是一样的。虽然python还和迭代器相关,那只是他们的实现方法不一样。本质上都是主程序和子程序互相切换执行。所以会了一种语言的协程,另外一种就像是买一赠一,好赚啊。