Lua协程

Lua 协程

概述

协程具有协同的性质,它允许两个或多个方法以某种可控的方式协同工作。在任何一个时刻,都只有一个协程在运行,只有当正在运行的协程主动挂起时它的执行才会被挂起(暂停)。

上面的定义可能看上去比较模糊。接下来让我讲得很清楚一点,假设我们有两个方法,一个是主程序方法,另一个是一个协程。当我们使用 resume 函数调用一个协程时,协程才开始执行。当在协程调用 yield 函数时,协程挂起执行。再次调用 resume 函数时,协程再从上次挂起的地方继续执行。这个过程一直持续到协程执行结束为止。

协程中可用的函数

下面的表中列出 Lua 语言为支持协程而提供的所有函数以及它们的用法。

S.N. 方法和功能
1 coroutine.create(f):用函数 f 创建一个协程,返回 thread 类型对象。
2 coroutine.resume(co[,val1,...]): 传入参数(可选),重新执行协程 co。此函数返回执行状态,也可以返回其它值。
3 coroutine.running():返回正在运行的协程,如果在主线程中调用此函数则返回 nil。
4 coroutine.status(co):返回指定协程的状态,状态值允许为:正在运行(running),正常(normal),挂起(suspended),结束(dead)。
5 coroutine.wrap(f):与前面 coroutine.create 一样,coroutine.wrap 函数也创建一个协程,与前者返回协程本身不同,后者返回一个函数。当调用该函数时,重新执行协程。
6 coroutine.yield(...):挂起正在执行的协程。为此函数传入的参数值作为执行协程函数 resume 的额外返回值(默认会返回协程执行状态)。

示例

让我们通过下面的例子来理解一下协程这个概念。

co = coroutine.create(function (value1,value2)
   local tempvar3 =10
   print("coroutine section 1", value1, value2, tempvar3)
   local tempvar1 = coroutine.yield(value1+1,value2+1)
   tempvar3 = tempvar3 + value1
   print("coroutine section 2",tempvar1 ,tempvar2, tempvar3)
   local tempvar1, tempvar2= coroutine.yield(value1+value2, value1-value2)
   tempvar3 = tempvar3 + value1
   print("coroutine section 3",tempvar1,tempvar2, tempvar3)
   return value2, "end"
end)

print("main", coroutine.resume(co, 3, 2))
print("main", coroutine.resume(co, 12,14))
print("main", coroutine.resume(co, 5, 6))
print("main", coroutine.resume(co, 10, 20))

执行上面的程序,我们可以得到如下的输出结果:

coroutine section 1	3	2	10
main	true	4	3
coroutine section 2	12	nil	13
main	true	5	1
coroutine section 3	5	6	16
main	true	2	end
main	false	cannot resume dead coroutine

上面的例子到底做了些什么呢?

和前面说到的一样,在例子中我们使用 resume 函数继续执行协程,用 yield 函数挂起协程。同样,从例子中也可以看出如何为执行协程的 resueme 函数返回多个值。下面我将逐步解释上面的代码。

  • 首先,我们创建了一个协程并将其赋给变量 co。此协程允许传入两个参数。
  • 第一次调用函数 resume 时,协程内局部变量 value1 和 value2 的值分别为 3 和 2。
  • 为了便于理解,我们使用了局部变量 tempvar3 该变量被初始化为 10。由于变量 value1 的值为3,所以 tempvar3 在随后的协程调用过程中被先后更新为 13 和 16。
  • 第一次调用 coroutine.yield 时,为 resume 函数返回了值 4 和 3,这两个值是由传入的参数 3,2 分别加 1 后的结果,这一点可以从 yield 语句中得到证实。除了显示指定的返回值外,resume 还收到隐式的返回值 true,该值表示协程执行的状态,有 true 和 false 两个可能取值。
  • 上面的例子中,我们还应该关注在下一次调用 resume 时如何为协程传入参数。从例子中可以看到,coroutine.yield 函数返回后为两个变量赋值,该值即是第二次调用 resume 时传入的参数。这种参数传递的机制让可以结合前面传入的参数完成很多新的操作。
  • 最后,协程中所有语句执行完后,后面的调用就会返回 false 状态,同时返回 "cannot resume dead coroutine"消息。

另一个协程的示例

下面这例子中的协程使用 yield 函数和 resume 函数依次返回数字 1 到 5。示例中,如果没有协程对象或对象已结束(dead),则重新创建一个新的协程对象;若协程已经存在,则执行已经存在的协程。

function getNumber()
   local function getNumberHelper()
      co = coroutine.create(function ()
      coroutine.yield(1)
      coroutine.yield(2)
      coroutine.yield(3)
      coroutine.yield(4)
      coroutine.yield(5)
      end)
      return co
   end
   if(numberHelper) then
      status, number = coroutine.resume(numberHelper);
      if coroutine.status(numberHelper) == "dead" then
         numberHelper = getNumberHelper()
         status, number = coroutine.resume(numberHelper);
      end
      return number
   else
      numberHelper = getNumberHelper()
      status, number = coroutine.resume(numberHelper);
      return number
   end
end
for index = 1, 10 do
   print(index, getNumber())
end

执行上述的程序,我们可以得到如下的输出结果:

1	1
2	2
3	3
4	4
5	5
6	1
7	2
8	3
9	4
10	5

大家经常会把协程和多线程编程语言中的线程进行对比,但我们要明白,协程有着与线程类似的特性,但是协程与线程的区别在于协程不能并发,任意时刻只会有一个协程执行,而线程允许并发的存在。(译注:译者认为本质上协程其是就是线程,不过是用户态的线罢了,它将调度问题交由程序开发人员手动完成。)

我们通过控制程序执行顺序以满足获取某些临时信息的需求。配合全局变量的使用,协和会变得更加的灵活方便。

糖果

糖果
LUA教程