从create到yield:Lua协程完全上手指南

一、核心概念:协程不是线程

  • 线程(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:协程成功执行,没有错误。后续的返回值是协程 yieldreturn 的值。
    • 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)))

三、双向通信:resumeyield 的数据交换

协程最强大的地方在于,resumeyield 可以双向传递数据,形成一个强大的通信管道。

  • 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 技术干货!如果觉得有用,记得收藏本文!

相关推荐
Z***u65931 分钟前
前端性能测试实践
前端
xhxxx35 分钟前
prototype 是遗产,proto 是族谱:一文吃透 JS 原型链
前端·javascript
倾墨36 分钟前
Bytebot源码学习
前端
用户938169125536039 分钟前
VUE3项目--集成Sass
前端
S***H2831 小时前
Vue语音识别案例
前端·vue.js·语音识别
啦啦9118861 小时前
【版本更新】Edge 浏览器 v142.0.3595.94 绿色增强版+官方安装包
前端·edge
蚂蚁集团数据体验技术2 小时前
一个可以补充 Mermaid 的可视化组件库 Infographic
前端·javascript·llm
LQW_home2 小时前
前端展示 接受springboot Flux数据demo
前端·css·css3
q***d1732 小时前
前端增强现实案例
前端·ar
IT_陈寒2 小时前
Vite 3.0 重磅升级:5个你必须掌握的优化技巧和实战应用
前端·人工智能·后端