Lua 协程
Lua - 协程
Section titled “Lua - 协程”Lua 中的协程(Coroutines)提供了一种协作式多任务(cooperative multitasking)的形式。与抢占式多任务系统(preemptive multitasking systems)中的线程(threads)不同,协程是显式地让出控制权(yield control),而不是被调度器(scheduler)中断。在任何给定的时间点,与一个 Lua 状态关联的协程中,只有一个在运行。一个协程可以暂停其执行(yield),之后可以从暂停的地方继续(resume)。
可以将协程想象成一个可以暂停执行然后继续执行的函数。当一个协程让出(yield)时,它可以将值传递回调用者(resumer)。当它被继续(resume)时,值可以传递给它,这些值将成为 coroutine.yield() 调用的结果。
协程对于以清晰和顺序的方式实现迭代器(iterators)、生成器(generators)、非阻塞 I/O 操作(non-blocking I/O operations)和状态机(state machines)非常有用。
核心协程函数
Section titled “核心协程函数”coroutine 库提供了以下关键函数:
| 函数签名 | 描述 |
|---|---|
coroutine.create(f) | 创建一个新的协程,并将给定函数 f 作为其主体。返回一个表示该协程的 ‘thread’ 对象。协程初始处于挂起(suspended)状态。 |
coroutine.resume(co [, val1, ...]) | 启动或继续协程 co 的执行。任何参数 val1, ... 会作为参数传递给协程函数(如果是首次 resume),或者作为 yield 调用的结果(如果 resume 一个已 yield 的协程)。成功时返回 true 和传递给 coroutine.yield(或协程函数返回)的任何值。失败或协程已完成时返回 false 和错误消息。 |
coroutine.yield(...) | 暂停调用协程的执行。传递给 yield 的任何参数都将作为相应 coroutine.resume 调用的结果返回。当被 resume 时,yield 会返回传递给 coroutine.resume 的任何值。 |
coroutine.status(co) | 返回协程 co 的状态字符串:"running"(当前正在运行的协程)、"suspended"(等待被 resume)、"normal"(活跃但不是当前运行的协程,例如已 yield 给另一个协程)或 "dead"(已完成执行或因错误停止)。 |
coroutine.wrap(f) | 类似于 coroutine.create,但返回一个函数,该函数被调用时会 resume 协程。与 coroutine.resume 返回错误状态不同,这个包装函数在协程遇到错误时会抛出错误。 |
coroutine.running() | 返回当前正在运行的协程(一个 ‘thread’ 对象)以及一个布尔值,该布尔值为 true 表示当前运行的协程是主协程。 |
coroutine.isyieldable() | (Lua 5.2+)如果当前运行的协程可以 yield,则返回 true。在 C 函数内部、pcall 或迭代器中不可 yield。 |
示例:基本的 yield 和 resume
Section titled “示例:基本的 yield 和 resume”让我们看一个简单的示例来说明其流程:
local co = coroutine.create(function(initial_a, initial_b) print(string.format("Coroutine: Received initial_a = %s, initial_b = %s", tostring(initial_a), tostring(initial_b))) local sum = initial_a + initial_b print("Coroutine: Calculated sum =", sum)
local resume_val1, resume_val2 = coroutine.yield(sum, "first_yield") -- Yield sum and a string print(string.format("Coroutine: Resumed with resume_val1 = %s, resume_val2 = %s", tostring(resume_val1), tostring(resume_val2))) local product = resume_val1 * resume_val2
local final_resume_val = coroutine.yield(product, "second_yield") -- Yield product and another string print("Coroutine: Resumed with final_resume_val =", final_resume_val)
return "Coroutine finished", product + final_resume_valend)
print("Main: Coroutine status:", coroutine.status(co)) -- suspended
local status, yield_val1, yield_val2 = coroutine.resume(co, 10, 5) -- Pass 10 and 5 to coroutineprint(string.format("Main: Resume 1 - Status: %s, YieldVal1: %s, YieldVal2: %s", tostring(status), tostring(yield_val1), tostring(yield_val2)))print("Main: Coroutine status:", coroutine.status(co)) -- suspended
local status2, yield_val3, yield_val4 = coroutine.resume(co, 3, 4) -- Pass 3 and 4 to the waiting yieldprint(string.format("Main: Resume 2 - Status: %s, YieldVal3: %s, YieldVal4: %s", tostring(status2), tostring(yield_val3), tostring(yield_val4)))print("Main: Coroutine status:", coroutine.status(co)) -- suspended
local status3, return_val1, return_val2 = coroutine.resume(co, 100) -- Pass 100 to the last waiting yieldprint(string.format("Main: Resume 3 - Status: %s, ReturnVal1: %s, ReturnVal2: %s", tostring(status3), tostring(return_val1), tostring(return_val2)))print("Main: Coroutine status:", coroutine.status(co)) -- dead
local status4, err_msg = coroutine.resume(co) -- Attempt to resume a dead coroutineprint(string.format("Main: Resume 4 - Status: %s, Error: %s", tostring(status4), tostring(err_msg)))运行上述程序时,输出将是(或类似):
Main: Coroutine status: suspendedCoroutine: Received initial_a = 10, initial_b = 5Coroutine: Calculated sum = 15Main: Resume 1 - Status: true, YieldVal1: 15, YieldVal2: first_yieldMain: Coroutine status: suspendedCoroutine: Resumed with resume_val1 = 3, resume_val2 = 4Main: Resume 2 - Status: true, YieldVal3: 12, YieldVal4: second_yieldMain: Coroutine status: suspendedCoroutine: Resumed with final_resume_val = 100Main: Resume 3 - Status: true, ReturnVal1: Coroutine finished, ReturnVal2: 112Main: Coroutine status: deadMain: Resume 4 - Status: false, Error: cannot resume dead coroutine示例解释:
- 创建了一个协程
co。它最初处于挂起状态(suspended)。 - 第一次
coroutine.resume(co, 10, 5):启动co,将10和5作为参数传递进去。协程打印、计算sum,然后yield出sum(15) 和 “first_yield”。控制权返回到main。 - 第二次
coroutine.resume(co, 3, 4):继续co。值3和4成为co内部coroutine.yield调用的结果。协程继续执行,计算product,然后yield出product(12) 和 “second_yield”。 - 第三次
coroutine.resume(co, 100):继续co。100成为第二次yield的结果。协程完成其主体并返回两个值。 - 协程现在处于
dead状态。尝试再次 resume 它会导致错误。
示例:使用 coroutine.wrap 创建生成器
Section titled “示例:使用 coroutine.wrap 创建生成器”coroutine.wrap 对于创建迭代器式函数(生成器)非常方便。
local function number_generator(limit) return coroutine.wrap(function() print("Generator started.") for i = 1, limit do print(string.format("Generator yielding %d", i)) coroutine.yield(i) end print("Generator finished.") -- No explicit return means it implicitly returns nothing when done. end)end
local next_num = number_generator(3)
print("Calling generator for the first time:")print("Received:", next_num()) -- Calls resume implicitly
print("\nCalling generator for the second time:")print("Received:", next_num())
print("\nCalling generator for the third time:")print("Received:", next_num())
print("\nCalling generator after it's finished:")-- Depending on Lua version and context, this might error or return nil.-- Typically, if wrapped function has no return, calling it after exhaustion returns nil.-- If it had a return, that would be returned. If it errors, wrap raises the error.local final_val = next_num()print("Received (final call):", final_val) -- Expected: nil after exhaustion-- print(next_num()) -- Calling again might error if it errored previously预期输出:
Calling generator for the first time:Generator started.Generator yielding 1Received: 1
Calling generator for the second time:Generator yielding 2Received: 2
Calling generator for the third time:Generator yielding 3Received: 3
Calling generator after it's finished:Generator finished.Received (final call): nil协程与线程的对比
Section titled “协程与线程的对比”区分 Lua 协程和操作系统线程是很重要的。协程是协作式的:它们一次只运行一个,并且只在当前运行的协程显式地 yield 时才切换。操作系统线程(OS threads)可以并发运行(在多核系统上),并通过操作系统进行抢占式调度。协程比操作系统线程轻量得多,并且在单个操作系统进程/线程内进行管理。
应用场景:
- **迭代器:**按需生成一系列值。
- **任务调度:**在单个 Lua 程序中实现简单的协作式任务调度器。
- **异步操作:**通过在等待时 yield 并在就绪时 resume 来模拟 I/O 或长时间计算的非阻塞行为(常用于 OpenResty 等 Web 服务器框架中)。
- **游戏 AI:**将复杂的 AI 行为实现为可以暂停和继续的状态机。
- **解析器:**编写可以 yield 中间结果或请求更多输入的解析器。
更多资源:Lua 官方参考手册提供了关于 coroutine 库及其行为的详细解释。