Lua - 协程

  • 介绍

    协程本质上是协作的,它允许两个或多个方法以受控方式执行。对于协程,在任何给定时间,只有一个协程运行,并且这个正在运行的协程仅在明确请求暂停时暂停其执行。
    上面的定义可能看起来很模糊。让我们假设我们有两种方法,一种是主程序方法,一种是协程。当我们使用 resume 函数调用协程时,它开始执行,当我们调用 yield 函数时,它暂停执行。同样,同一个协程可以从它挂起的地方继续执行另一个恢复函数调用。这个过程可以一直持续到协程执行结束。
  • 协程中可用的函数

    下表列出了 Lua 中协程的所有可用函数及其对应的用途。
    序号 方法和目的
    1
    coroutine.create (f)
    创建一个带有函数 f 的新协程,并返回一个“线程”类型的对象。
    2
    coroutine.resume (co [, val1, ...])
    恢复协程 co 并传递参数(如果有)。它返回操作状态和可选的其他返回值。
    3
    coroutine.running ()
    如果在主线程中调用,则返回正在运行的协程或 nil。
    4
    coroutine.status (co)
    根据协程的状态返回运行、正常、暂停或死的值之一。
    5
    coroutine.wrap (f)
    与 coroutine.create 一样,coroutine.wrap 函数也创建了一个协程,但它不是返回协程本身,而是返回一个函数,该函数在调用时恢复协程。
    6
    coroutine.yield (...)
    暂停正在运行的协程。传递给此方法的参数充当恢复函数的附加返回值。
  • 例子

    让我们看一个例子来理解协程的概念。
    
    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函数来停止操作。另外,你可以看到协程的resume函数收到了多个返回值。
    • 首先,我们创建一个协程并将其分配给一个变量名 co,该协程接受两个变量作为其参数。
    • 当我们调用第一个 resume 函数时,值 3 和 2 会保留在临时变量 value1 和 value2 中,直到协程结束。
    • 为了让您理解这一点,我们使用了一个 tempvar3,它最初是 10,它会通过协程的后续调用更新为 13 和 16,因为 value1 在整个协程执行过程中保持为 3。
    • 第一个 coroutine.yield 将两个值 4 和 3 返回给 resume 函数,我们通过更新 yield 语句中的输入参数 3 和 2 得到它们。它还接收协程执行的真/假状态。
    • 关于协程的另一件事是如何处理恢复调用的下一个参数,在上面的例子中;您可以看到变量 coroutine.yield 接收下一个调用 params,它提供了一种强大的方式来执行新操作并保留现有参数值。
    • 最后,一旦协程中的所有语句都执行完毕,后续调用将返回 false 和“无法恢复死协程”语句作为响应。
  • 另一个协程示例

    让我们看一个简单的协程,它在 yield 函数和 resume 函数的帮助下返回一个从 1 到 5 的数字。如果不可用,它会创建协程,否则会恢复现有的协程。
    
    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
    
    协程和多道程序语言的线程经常有比较,但是我们要明白协程和线程有相似的特点,只是一次只执行一个,从不并发执行。
    我们控制程序执行顺序以满足需要,并提供临时保留某些信息。在协程中使用全局变量为协程提供了更大的灵活性。