一、核心概念:协程不是线程
- 线程(Preemptive Multitasking) :由操作系统强制调度。一个线程可以在任何时刻被操作系统中断,切换到另一个线程。这是抢占式的,程序员无法精确控制切换时机。它能利用多核 CPU 实现真正的并行计算,但也带来了锁、竞态条件等复杂性。
- 协程(Cooperative Multitasking) :由程序自己控制调度。一个协程除非自己主动让出(yield)控制权,否则它会一直运行下去。这是协同式的。它运行在单一线程中,不涉及真正的并行,因此没有数据同步的烦恼,是一种轻量级的并发模型。
二、协程的四大核心API
Lua 的协程操作主要围绕 coroutine 这个全局表中的四个函数展开。
1. coroutine.create(f) -- 创建一个新的协程
它接受一个函数 f 作为参数(协程的"主体"),并返回一个 thread 类型的对象,代表这个新创建的协程。此时,协程处于suspended暂停状态,并不会立即执行。
lua
local co = coroutine.create(function()
print("Hello from coroutine!")
return 1
end)
print(type(co)) -- 输出: thread
print(coroutine.status(co)) -- 输出: suspended
2. coroutine.resume(co, ...) -- 启动或恢复一个协程的执行
它会执行协程 co,直到协程主动 yield 或者执行完毕。
- 返回值 :第一个返回值是一个布尔值,表示执行是否成功。第二个返回值如下:
true:协程成功执行,没有错误。后续的返回值是协程yield或return的值。false:协程执行出错。后续的返回值是错误信息。
lua
-- 第一次 resume,启动协程
local success, result = coroutine.resume(co)
-- 输出: Hello from coroutine!
print(success) -- 输出: true
print(result) -- 输出:1
print(coroutine.status(co)) -- 输出: dead (因为函数执行完了)
3. coroutine.yield(...) -- 暂停协程的执行,并交还控制权
只能在协程内部调用。当协程 yield 时,它的执行会立刻暂停,而 coroutine.resume 的调用会立即返回。
lua
local co = coroutine.create(function()
print("co: step 1")
coroutine.yield() -- 暂停!
print("co: step 2")
end)
coroutine.resume(co) -- 输出 co: step 1,然后暂停
print(coroutine.status(co)) -- 输出: suspended
coroutine.resume(co) -- 从上次暂停的地方继续,输出 co: step 2
4. coroutine.status(co) -- 获取协程的状态
返回一个字符串,表示协程 co 的当前状态:
"suspended": 协程被创建或yield后,处于暂停等待状态。"running": 协程正在运行。(你很难直接观察到这个状态,因为它总是在执行代码)。"dead": 协程执行完毕或因错误终止。"normal": 表示一个协程是活动的,但并不在运行。(这个状态比较特殊,一般指的是协程A启动了协程B,此时协程A的状态为normal状态)
normal状态示例:
lua
-- 1. 前向声明 (Forward Declaration)
-- 我们先声明 main_co 变量,这样在 sub_co 的定义中就可以安全地引用它。
local main_co
-- 2. 创建子协程
-- 它的函数体现在可以捕获到 main_co 变量了。
local sub_co = coroutine.create(function(name)
print(string.format("子协程 (%s): 开始执行。", name))
-- 关键点: 检查正在恢复我们的 main_co 的状态。
-- 因为 main_co 正在等待我们 yield,所以它的状态应该是 'normal'。
local resumer_status = coroutine.status(main_co)
print(string.format("子协程 (%s): 此时,恢复我的那个协程状态是 '%s'。", name, resumer_status))
-- 挂起自己,将控制权交还给 main_co
coroutine.yield("第一次挂起的结果")
print(string.format("子协程 (%s): 我被第二次恢复了。", name))
return "执行完毕" -- 返回一个最终结果
end)
-- 3. 创建主协程并赋值给之前声明的变量
main_co = coroutine.create(function()
print("主协程: 开始执行。")
-- 在恢复子协程之前,检查其状态
print(string.format("主协程: [恢复前] sub_co 的状态是 '%s'。", coroutine.status(sub_co)))
-- 恢复子协程
print("主协程: --> 正在恢复 sub_co ... (此时主协程将进入 'normal' 状态)")
local success, result = coroutine.resume(sub_co, "sub_co_01")
print("主协程: <-- 从 sub_co 返回。")
print(string.format("主协程: sub_co 第一次恢复的结果是 (成功? %s), 返回值: '%s'", tostring(success), result))
-- 在子协程挂起后,再次检查其状态
print(string.format("主协程: [挂起后] sub_co 的状态是 '%s'。", coroutine.status(sub_co)))
-- 优化: 使用 coroutine.running() 来获取当前协程,避免了自我引用。
print(string.format("主协程: 我自己当前的状态是 '%s'。", coroutine.status(coroutine.running())))
-- 再次恢复 sub_co,让它执行完毕
print("主协程: --> 再次恢复 sub_co,让它跑完...")
success, result = coroutine.resume(sub_co)
print("主协程: <-- sub_co 已结束。")
print(string.format("主协程: sub_co 第二次恢复的结果是 (成功? %s), 返回值: '%s'", tostring(success), result))
print(string.format("主协程: [结束后] sub_co 的状态是 '%s'。", coroutine.status(sub_co)))
print("主协程: 执行完毕。")
end)
-- 4. 启动整个流程
print("--- 开始运行主协程 ---")
coroutine.resume(main_co)
print("--- 主协程已结束 ---")
-- 最终状态验证
print(string.format("\n[最终状态] main_co 的状态是: '%s'", coroutine.status(main_co)))
print(string.format("[最终状态] sub_co 的状态是: '%s'", coroutine.status(sub_co)))
三、双向通信:resume 与 yield 的数据交换
协程最强大的地方在于,resume 和 yield 可以双向传递数据,形成一个强大的通信管道。
coroutine.resume(co, val1, val2): 传递给resume的额外参数,会成为协程启动时函数的参数 ,或者yield的返回值。local ret1, ret2 = coroutine.yield(val1, val2):yield的参数,会成为resume的返回值。
让我们来看一个经典的"生产者-消费者"模型:
lua
-- 生产者协程:不断生产数字
local producer = coroutine.create(function()
for i = 1, 3 do
print("Producer: generating", i)
coroutine.yield(i) -- 暂停,并把数字 i 发送出去
end
end)
-- 消费者代码 (在主流程中)
while true do
local success, value = coroutine.resume(producer)
if not success then
print("Consumer: error:", value)
break -- 如果执行失败,退出
end
if coroutine.status(producer) == "dead" and value == nil then
print("Consumer: producer is done.")
break -- 如果协程已死且没有返回值,说明正常结束
end
print("Consumer: received", value)
end
-- 输出:
-- Producer: generating 1
-- Consumer: received 1
-- Producer: generating 2
-- Consumer: received 2
-- Producer: generating 3
-- Consumer: received 3
-- Consumer: producer is done.
结语
点个赞,关注我获取更多实用 Lua 技术干货!如果觉得有用,记得收藏本文!