Lua协程(一)

Lua协程(一)

本文主要涉及Lua协程是如何工作的,并不涉及Lua协程的工作原理。

本人理解能力有限,一直没有弄懂Lua协程是如何工作的。今天,仔细看看了帮助文档,有些感觉,记录在此。

先来看看《Lua程序设计》中是如何讲解Lua协程的:

协同程序与多线程的线程比较类似:有自己的堆栈,自己的局部变量,有自己的指令指针,但是和其它协同程序共享全局变量等很多信息。线程和协同程序的主要不同在于:在多处理器情况下,从概念上讲多线程程序同时运行多个线程,而协同程序是通过协作来完成的,在任一指定时刻只有一个协同程序在运行,并且这个正在运行的协同程序只有在明确的被要求挂起的时候才会被挂起。

lua通过table提供了所有的协同函数,create函数创建一个新的协同程序,create只有一个参数;协同程序将要运行的代码封装成函数,返回值为thread类型的值表示创建了一个新的协同程序。协同有三个状态:挂起态(suspended)、运行态(running)、死亡(dead)。注意的是: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"))

output:
     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

再来看看,参考文档对coroutine.create coroutine.resume coroutine.yield几个函数的解释:

coroutine.create(f):创建一个新的协程,协程体中的内容是f,f必须是一个Lua函数。该函数返回这个新创建的类型为thread的协程。

coroutine.resume(co [, val1, …]):当首次resume一个协程的时候,便开始运行这个协程中的函数f,参数val1, … 传递给函数f作为f的参数。当协程挂起(yield)过,再次resume这个协程的时候,参数val1, … 作为yield的返回结果;如果协程运行过程中没有发生错误,那么resume返回true以及yield传递过来的值(当协程挂起时),或者从函数f返回的任何值(当协程终止时)。如果协程运行过程中发生错误,那么返回false以及错误信息。

coroutine.yield(…):将正在执行的线程挂起(暂停)。yield的任何参数将传递给resume,作为resume额外的返回结果。

现在,我们来看看,上面的示例具体是如何运行的。

函数foo函数中有一个线程挂起,那么它是对哪个线程进行挂起呢,上文引用中有说在任一指定时刻只有一个协同程序在运行,并且这个正在运行的协同程序只有在明确的被要求挂起的时候才会被挂起,所以如果有某个协程程序调用了该函数,那么该函数执行到yield时,会对这个正在运行的协程挂起。

co=coroutine.create创建了一个协程,协程创建后并不自动运行,可以看成在协程体中的函数f的入口处挂起了。

print('main', coroutine.resume(co, 1, 10)):第一次resume协程co,它的参数会作为协程co中函数的参数,即a=1, b=10。此时运行协程co,执行到print('co-body', a, g)会打印出co-body 1 10。接着,执行到语句local r = foo(a+1),调用函数fooprint('foo', a)打印出foo 2;接着执行foo中的return coroutine.yield(2*a) 对当前的协程挂起,协程co就暂停在这里,yield的参数返回到调用的resume的地方,此时print('main', coroutine.resume(co, 1, 10))打印出main true 4。从协程co开始执行,到第一次挂起时,所执行的流程(打印结果)如下:

cobody  1   10
foo     2
main    true    4

print('main', coroutine.resume(co, 'r')):再次重新启用协程,此时协程还没有死亡,继续重新从上次yield处运行co中的函数。resume将参数‘r’传给return coroutine.yield(2*a)作为yield的返回结果,所以r='r',下面一条语句答应出co-body r。接着语句local r,s = coroutine.yield(a+b, a-b)又在此处挂起当前协程,yield将参数11 -9传给调用此次resume的地方,print('main', coroutine.resume(co, 'r'))打印出main true 11 -9。从第二次resume,到第二期yield,所执行的流程(打印结果)如下:

co-body     r
main    true 11 -9

print('main', coroutine.resume(co, 'x', 'y')):再次重新启用协程,此时协程还没有死亡,继续重新从上次yield处运行协程co中的函数。resume将参数x y传给yield,作为yield的返回结果,此时r='x', s='s'。接着print('co-body', r, s'打印出co-body r sreturn b, 'end'返回'10 end',协程终止(死亡)。print('main', coroutine.resume(co, 'x', 'y'))打印出main true 10 'end'。从第三次resume到协程终止,所执行的流程(打印结果)如下:

co-body x   y
main    true    10  end

print('main', coroutine.resume(co, 'x', 'y')):再次resume协程co,但此时协程co已经死亡,所以resume返回false,以及错误信息'cannot resume dead coroutine'。此次resume的流程如下:

main    false   cannot resume dead coroutine

总结下来,从协程创建,执行,到死亡的过程如下:
1. 创建协程,协程创建后并自动运行,可以看成在协程体中函数的入口处挂起。
2. 第一次resume协程时,resume的参数传递给协程体中的函数,该函数一直执行,直到初次遇到yield时挂起,此时协程体中的函数暂停在此处,保留协程体的堆栈,局部变量,指令指针等信息,以使协程下次重此处重新唤起。而yield的参数会作为,resume的额外的返回结果。
3. 再次resume该协程时,resume的参数会作为上次yield的返回结果,直到再次遇到yield时挂起。同步骤2,以此类推,知道协程终止。
4. 协程终止后,销毁该协程的堆栈、局部变量与指令指针等信息。

参考
1. 《Lua程序设计》,Roberto Ierusalimschy,译:周惟迪 。
2. Lua official website

原文地址:https://www.cnblogs.com/corfox/p/5414983.html