第一版是针对 Lua 5.0 编写的。尽管在很大程度上仍然适用于后续版本,但还是有一些差异。
第四版针对 Lua 5.3,可在 亚马逊 和其他书店购买。
购买本书,您还可以帮助 支持 Lua 项目


9.1 – 协程基础

Lua 将其所有协程函数打包在 coroutine 表中。create 函数创建新的协程。它有一个参数,即包含协程将运行的代码的函数。它返回类型为 thread 的值,该值表示新协程。通常,create 的参数是匿名函数,如下所示

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

协程可以处于三种不同的状态之一:挂起、运行和死亡。当我们创建协程时,它从挂起状态开始。这意味着当我们创建协程时,它不会自动运行其主体。我们可以使用 status 函数检查协程的状态

    print(coroutine.status(co))   --> suspended
函数 coroutine.resume (re)启动协程的执行,将其状态从挂起更改为运行
    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)    -- prints nothing
在最后一次调用 resume 时,协程主体完成了循环然后返回,因此协程现在已死亡。如果我们尝试再次恢复它,resume 将返回 false 加上错误消息
    print(coroutine.resume(co))
    --> false   cannot resume dead coroutine
请注意,resume 在受保护模式下运行。因此,如果协程内部有任何错误,Lua 不会显示错误消息,而是会将其返回给 resume 调用。

Lua 中的一个有用功能是 resume-yield 对可以相互交换数据。第一个 resume 没有对应的 yield 等待它,它将它的额外参数作为协程主函数的参数传递

    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 提供了我称之为非对称协程的东西。这意味着它有一个函数来暂停协程的执行,还有一个不同的函数来恢复已暂停的协程。一些其他语言提供对称协程,其中只有一个函数可以将控制权从任何协程转移到另一个协程。

有些人称非对称协程为半协程(因为它们不对称,所以它们并不是真正的协程)。然而,其他人使用相同的术语半协程来表示协程的受限实现,其中协程只能在其不在任何辅助函数中时暂停其执行,即当其控制堆栈中没有挂起调用时。换句话说,只有此类半协程的主体才能产生。Python 中的生成器是半协程这种含义的一个例子。

与对称协程和非对称协程之间的差异不同,协程和生成器(如 Python 中所示)之间的差异是深刻的;生成器根本不够强大,无法实现我们可以用真正的协程编写的几个有趣的构造。Lua 提供真正的非对称协程。那些喜欢对称协程的人可以在 Lua 的非对称功能之上实现它们。这是一项简单的任务。(基本上,每个传输都执行一个 yield,然后执行一个 resume。)