协程的状态与生命周期
每个协程都有自己的生命周期:从创建、运行、挂起,到最终死亡。coroutine.status 可以随时查询一个协程当前所处的状态,理解这些状态及其转换条件,是避免"恢复已死亡协程"这类错误的基础。
1. 协程的四种状态
coroutine.status(co) 返回以下四种字符串之一:
| 状态 | 含义 |
|---|---|
suspended | 挂起:已创建尚未运行,或停在某个 yield 处等待恢复 |
running | 运行中:正在占用执行权(只能在被查询协程自身内部看到) |
normal | 活跃但未运行:该协程恢复过别的协程,自己正等待对方让出或结束 |
dead | 死亡:函数已返回或发生了无法恢复的错误,也可能是被 close 关闭 |
2. 状态流转实例
下面的例子完整地走了一遍"挂起 → 运行 → 挂起 → 死亡"的流程:
local co -- 先声明再赋值,协程体内才能引用这个局部变量
co = coroutine.create(function()
print("inside:", coroutine.status(co)) -- 运行中看自己是 running
coroutine.yield()
print("resumed again")
end)
print(coroutine.status(co)) -- 输出 suspended(创建后尚未启动)
coroutine.resume(co) -- 输出 inside: running
print(coroutine.status(co)) -- 输出 suspended(挂起在 yield 处)
coroutine.resume(co) -- 输出 resumed again
print(coroutine.status(co)) -- 输出 dead(函数返回)normal 状态比较少见:当一个协程 A 恢复另一个协程 B 时,B 处于 running,而 A 就处于 normal。它"活着",但执行权在别人手里:
local main = coroutine.running() -- 当前(主)协程
local co = coroutine.create(function()
-- 此时 co 是 running,恢复它的 main 是 normal
print("status of main:", coroutine.status(main))
end)
coroutine.resume(co) -- 输出 status of main: normal3. 查询正在运行的协程
coroutine.running 返回当前正在运行的协程本身,以及它是否为主协程:
local co, is_main = coroutine.running()
print(co ~= nil, is_main) -- 在脚本主流程中执行:输出 true true主协程是程序启动时就存在的协程,它永远不会是 suspended 状态(不能对它 resume),程序退出时它才结束。
4. 死亡协程与恢复错误
协程死亡之后,再对它执行 resume 会得到一个错误而不是崩溃:
local co = coroutine.create(function() end)
coroutine.resume(co) -- 协程运行完毕,进入 dead 状态
print(coroutine.status(co)) -- 输出 dead
print(coroutine.resume(co)) -- 输出 false cannot resume dead coroutine同理,尝试恢复主协程、或恢复一个正在运行中的协程,也都会返回失败。稳妥的写法是始终检查 resume 的第一个返回值。
5. 用 coroutine.close 提前结束
Lua 5.4 增加了 coroutine.close,可以把一个挂起中的协程直接置为死亡状态:
local co = coroutine.create(function()
coroutine.yield("waiting")
print("never reached")
end)
coroutine.resume(co) -- 输出 true waiting
print(coroutine.close(co)) -- 输出 true
print(coroutine.status(co)) -- 输出 deadclose 的价值在于资源清理:关闭时,协程内尚未销毁的 to-be-closed 变量(<close> 局部变量)会按逆序调用各自的 __close 元方法,适合释放文件句柄、数据库连接等资源。如果某个 __close 抛出了错误,close 返回 false 和错误对象。
6. 生命周期小结
一个协程的典型生命周期可以概括为:
coroutine.create创建,进入suspended;coroutine.resume启动,进入running;- 遇到
coroutine.yield,回到suspended,等待下一次恢复; - 重复 2-3,直到函数
return(或发生错误、被close),进入dead; dead之后的任何resume都会失败。
结语
协程的状态机并不复杂:suspended 与 running 之间靠 resume/yield 来回切换,dead 是唯一不可逆的终点。写代码时不要恢复已死亡的协程;需要提前结束时调用 close,让 to-be-closed 变量完成清理。