《Lua程序设计》9.1 协同程序基础 学习笔记

协同程序(coroutine)与线程(thread)差不多,也就是一条执行序列,拥有自己独立的栈、局部变量和指令指针,同时又与其他协同程序共享全局变量和其他大部分东西。从概念上讲线程与协同程序的主要区别在于,一个具有多个线程的程序可以同时运行几个线程,而协同程序却需要彼此协作地运行。就是说,一个具有多个协同程序的程序在任意时刻只能运行一个协同程序,并且正在运行的协同程序只会在其显式地要求挂起(suspend)时,它的执行才会暂停。

Lua将所有关于协同程序的函数放置在一个名为“coroutine”的table中。函数create用于创建新的协同程序,它只有一个参数,就是一个函数。该函数的代码就是协同程序所需执行的内容。create会返回一个thread类型的值,用以表示新的协同程序。通常create的参数是一个匿名函数,例如:

co = coroutine.create(function () print("hi") end)
print(co)    --> thread: 0x8071d98

一个协同程序可以处于4种不同的状态:挂起(suspended)、运行(running)、死亡(dead)和正常(normal)。当创建一个协同程序时,它处于挂起状态。也就是说,协同程序不会在创建它时自动执行其内容。可以通过函数status来检查协同程序的状态:

print(coroutine.status(co))    --> suspended

函数coroutine.resume用于启动或再次启动一个协同程序的执行,并将其状态由挂起改为运行:

coroutine.resume(co)    --> hi

在本例中,协同程序的内容指示简单地打印了“hi”后便终止了,然后它就处于死亡状态,也就再也无法返回了:

print(coroutine.status(co))    --> dead

到目前为止,协同程序看上去还指示像一种复杂的函数调用方法。骑士协同程序的真正强大指出在于函数yield的使用上,该函数可以让一个运行中的协同程序挂起,而之后可以再恢复它的运行。请看下面的这个简单的示例:

co = coroutine.create(function ()
    for i=1,10 do
        print("co", i)
        coroutine.yield()
    end
end)

现在当唤醒这个协同程序时,它就会开始执行,知道第一个yield:

coroutine.resume(co)    --> co  1

如果此时检查器状态,会发现协同程序处于挂起状态,因此可以再次恢复其运行:

print(coroutine.status(co))    --> suspended

从协同程序的角度看,所有在它挂起时发生的活动都发生在yield调用中。当恢复协同程序的执行时,对于yield的调用才最终返回。然后协同程序继续执行它的执行,知道下一个yield调用或执行结束:

coroutine.resume(co)    --> co  2
coroutine.resume(co)    --> co  3
...
coroutine.resume(co)    --> co  10
coroutine.resume(co)    -- 什么都不打印

在最后一次调用resume时,协同程序的内容已经执行完毕,并已经返回。因此,这时协同程序处于死亡状态。如果试图再次恢复它的执行,resume将false及一条错误消息:

print(coroutine.resume(co))    --> false  cannot resume dead coroutine

resume是在保护模式中运行的。因此,如果在一个协同程序的执行中发生任何错误,Lua是不会显示错误消息的,而是将执行权返回给resume调用。

当一个协同程序A唤醒另一个协同程序B时,协同程序A就处于一种特殊状态,既不是挂起状态(无法继续A的执行),也不是运行状态(是B在运行)。所以将这时的状态称为“正常”状态。

Lua的协同程序还具有一项有用的机制,就是可以通过一对resume-yield来交换数据。在第一次调用resume时,并没有对应的yield在等待它,因此所有传递给resume的额外参数都将视为协同程序主函数的参数:

co = coroutine.create(function (a,b,c)
    print("co", a,b,c)
end)
coroutine.resume(co, 1, 2, 3)   --> co  1  2  3

在resume调用返回的内容中,第一个值为true则表示没有错误,而后面所有的值都是对应yield传入的参数:

co = coroutine.create(function (a,b)
    coroutine.yield(a+b, a-b)
end)
print(coroutine.resume(co, 20, 10)) --> true  30  10

与此对应的是,yield返回的额外值就是对应resume传入的参数:

co = coroutine.create (function () 
    print("co", coroutine.yield())
end)
coroutine.resume(co)
coroutine.resume(co, 4, 5)  --> co  4  5

最后,当一个协同程序结束时,它的主函数所返回的值都将作为对应resume的返回值:

co = coroutine.create(function ()
   return 6, 7 
end)
print(coroutine.resume(co))     --> true  6  7

很少在同一个协同程序中使用所有这些功能,但每种功能各有其用途。

Lua提供的是一种“非对称的协同程序(asymmetric coroutine)”。也就是说,Lua提供了两个函数来控制协同程序的执行,一个用于挂起执行,另一个用于恢复执行。而一些其他的语言则提供了“堆成的协同程序(symmetric coroutine)”,其中只有一个函数用于转让协同程序之间的执行权。