# 对 Lua 使用协程的调研 #

学习参考:

http://blog.csdn.net/soloist/article/details/329381
http://www.cnblogs.com/zrtqsk/p/4374360.html

# 写在前面

对于线程:当我们在程序中创建多线程的时候,看起来,同一时刻多个线程是同时执行的,不过实质上多个线程是并发的,因为只有一个CPU,所以实质上同一个时刻只有一个线程在执行。在一个时间片内执行哪个线程是不确定的,我们可以控制线程的优先级,不过真正的线程调度由CPU的调度决定。(抢占式多任务)

对于协程:协程跟线程都代表一个执行序列。不同的是,协程把线程中不确定的地方尽可能的去掉,执行序列间的切换不再由CPU隐藏的进行,而是由程序显式的进行。所以,使用协程实现并发,需要多个协程彼此协作。(协作式多任务)

下图是非首次resume协程的情况下,resume和yield的互相调用的情况。

如果是首次resume协程,那么resume的参数会直接传递给协程函数。Lua协程的核心就是resume和yield的协作。

# coroutine 库

coroutine.create(f) : 传一个函数参数,用来创建协程。返回一个 “thread” 对象。

coroutine.isyieldable() : 如果正在运行的协程可以让出,则返回真,值得注意的是,只有主协程(线程)和C函数中是无法让出的。

coroutine.resume(co,[Val1,...]) : 用来启动或再次启动一个协程,使其由挂起状态变成运行状态。resume 函数相当于正在执行协程中的方法。
参数Val1…是执行协程co时传递给协程的方法。
(1)首次执行协程 co 的时候,参数 val1… 会传递给协程 co 的函数;
(2)再次执行协程 co 的时候,参数 val1… 会作为给协程 co 中上一次 yield 的返回值。
这是协程的核心。

resume函数返回有3种情况:
(1)如果协程co的函数执行完毕,协程正常终止,resume返回true和函数的返回值;
(2)如果协程co的函数正在执行的过程中,协程让出了(调用了yield()方法),resume返回true和协程中调用yield传入的参数;
(3)如果协程co的函数执行过程中发生了错误,resume返回false和错误消息;

resume在保护模式下执行的,所以resume不会导致程序崩溃。

coroutine.running() : 用来判断当前执行的协程是不是主线程,如果是就返回true;

coroutine.status(co) : 返回协程co的状态,有四种状态:
running – 如果在协程的函数中调用status,传入协程自身的句柄,那么执行到这里的时候才会返回running状态。
suspended – 如果协程还未结束,即自身调用了yeild或还没开始运行,那么就是suspended状态。
normal – 如果 协程A resume 协程B 时,协程A处于的状态为normal。在协程B的执行过程中,协程A就一直处于normal状态。因为它这时候既不是挂起状态、也不是运行状态。
dead。如果一个协程发生错误结束,或正常终止。那么就处于dead状态。如果这时候对它调用resume,将返回false和错误消息。

coroutine.wrap(f) : 也是用来创建协程的,只不过这个协程的句柄是隐藏的。
与create()的区别:
(1)wrap()返回的是一个函数,每次调用这个函数相当于调用coroutine.resume()。
(2)调用这个函数相当于在执行resume()函数。
(3)调用这个函数时传入的参数,就相当于在调用resume时传入的除协程的句柄外的其他参数。
(4)调用这个函数时,跟resume不同的是,它并不是在保护模式下执行的,若执行崩溃会直接向外抛出。

coroutine.yield(...) : 使正在执行的函数挂起。
注意:传递给yield的参数会作为resume的额外返回值;同时,如果对该协程不是第一次执行resume,resume函数传入的参数将会作为yield的返回值。

# 例子

1
2
3
4
5
6
7
8
9
10
11
12
13
-- resume 和 yield
coco = coroutine.create(
function(a,b)
print("resume args:" .. a .. " , " .. b)
yreturn = coroutine.yield()
print("yield return:" .. yreturn)
end
)

coroutine.resume(coco, 0,1)
coroutine.resume(coco, 21)
-- 输出: resume args:0 , 1
-- 输出: yield return:21

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-- wrap
coco2 = coroutine.wrap(
function(a,b)
print("resume args:"..a..","..b)
yreturn = coroutine.yield()
print("yreturn :"..yreturn)
end
)
print(type(coco2))
coco2(0,1)
coco2(21)

-- 输出:
-- function
-- resume args:0,1
-- yreturn :21

wrap的使用显得更方便

再一个例子:

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

-- main.lua

function status()
print("co1's status: "..coroutine.status(co1).." , co2's status: "..coroutine.status(co2))
end

co1 = coroutine.create(function(a)
print("arg is: "..a)
status()
local stat,rere = coroutine.resume(co2,"2")
print("resume's return is :"..rere)
status()
local stat2, rere2 = coroutine.resume(co2,"4")
print("resume's return is :"..rere2)
local arg = coroutine.yield("6")
end)

co2 = coroutine.create(
function(a)
print("arg is :"..a)
status()
local rey = coroutine.yield("3")
print("yield's return is :"..rey)
status()
coroutine.yield("5")
end
)

-- 主线程执行co1,传入字符串“main thread arg”
stat,mainre = coroutine.resume(co1,"1")
status()
print("last return is :"..mainre)

main()

输出结果:

1
2
3
4
5
6
7
8
9
10
11
arg is: 1
co1's status: running , co2's status: suspended
arg is :2
co1's status: normal , co2's status: running
resume's return is :3
co1's status: running , co2's status: suspended
yield's return is :4
co1's status: normal , co2's status: running
resume's return is :5
co1's status: suspended , co2's status: suspended
last return is :6

再一个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

--A.lua
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"))

A()

输出结果:

1
2
3
4
5
6
7
8
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