强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

11 - 协程 / Coroutines

协程 / Coroutines

Lua 的协程是非抢占式的——一个协程必须主动 yield 才会让出控制权。这使得并发逻辑变得可预测、易调试,完全不需要锁。

Lua’s coroutines are non-preemptive — a coroutine must explicitly yield to give up control. This makes concurrent logic predictable and debuggable, with no locks needed.


🟢 基础 / Basics — 创建和运行协程

1. 基本用法 / Basic Usage

-- 创建协程
local co = coroutine.create(function()
    print("Hello from coroutine!")
    coroutine.yield()    -- 暂停执行
    print("Resumed!")
    coroutine.yield("some data")    -- 暂停并返回数据
    print("Done!")
end)

-- 协程有三种状态:suspended, running, dead
print(coroutine.status(co))    -- suspended

-- 启动/恢复协程
coroutine.resume(co)           -- Hello from coroutine!
print(coroutine.status(co))    -- suspended(被 yield 暂停)

coroutine.resume(co)           -- Resumed!
print(coroutine.status(co))    -- suspended

coroutine.resume(co)           -- Done!
print(coroutine.status(co))    -- dead(执行完毕)

2. yield 传递数据 / Passing Data with yield

-- resume → yield: 传递参数给 yield
local co = coroutine.create(function()
    local a, b = coroutine.yield()    -- 接收 resume 传入的参数
    print("Received:", a, b)
end)

coroutine.resume(co)              -- 启动,执行到 yield 暂停
coroutine.resume(co, 10, 20)     -- 恢复,传入 10, 20

-- yield → resume: 返回值给 resume
local co2 = coroutine.create(function()
    coroutine.yield("hello", "world")
    return "done"
end)

local ok, a, b = coroutine.resume(co2)
print(ok, a, b)    -- true  hello  world

local ok2, result = coroutine.resume(co2)
print(ok2, result)    -- true  done

3. 简单的迭代器 / Simple Iterator

-- 用协程实现遍历器
function range(start, stop, step)
    return coroutine.wrap(function()
        for i = start, stop, step or 1 do
            coroutine.yield(i)
        end
    end)
end

-- coroutine.wrap 返回一个函数,调用即 resume
for i in range(1, 5) do
    print(i)    -- 1, 2, 3, 4, 5
end

🟡 进阶 / Intermediate — 实用协程模式

1. 生产者-消费者模式

-- 经典的生产者-消费者问题
local function producer()
    return coroutine.create(function()
        while true do
            local item = "item_" .. math.random(100)
            print("[Producer] Produced: " .. item)
            coroutine.yield(item)    -- 生产一个,交给消费者
        end
    end)
end

local function consumer(prod_co)
    for i = 1, 5 do
        local ok, item = coroutine.resume(prod_co)
        print("[Consumer] Consumed: " .. item)
    end
end

consumer(producer())
-- [Producer] Produced: item_42
-- [Consumer] Consumed: item_42
-- ...

2. 状态机 / State Machine

-- 用协程实现状态机(比 goto 更清晰)
local function trafficLight()
    return coroutine.wrap(function()
        while true do
            for _ = 1, 3 do coroutine.yield("GREEN") end
            for _ = 1, 2 do coroutine.yield("YELLOW") end
            for _ = 1, 3 do coroutine.yield("RED") end
        end
    end)
end

local light = trafficLight()
for i = 1, 10 do
    print(i, light())
end
-- 1  GREEN
-- 2  GREEN
-- 3  GREEN
-- 4  YELLOW
-- 5  YELLOW
-- 6  RED
-- ...

3. 协作式多任务 / Cooperative Multitasking

-- 简单的协程调度器
local scheduler = {tasks = {}}

function scheduler:add(task)
    self.tasks[#self.tasks + 1] = coroutine.create(task)
end

function scheduler:run()
    while #self.tasks > 0 do
        local newTasks = {}
        for _, co in ipairs(self.tasks) do
            if coroutine.status(co) ~= "dead" then
                local ok, err = coroutine.resume(co)
                if not ok then
                    print("Task error: " .. err)
                else
                    newTasks[#newTasks + 1] = co
                end
            end
        end
        self.tasks = newTasks
    end
end

-- 使用
scheduler:add(function()
    for i = 1, 3 do
        print("Task A: step " .. i)
        coroutine.yield()
    end
end)

scheduler:add(function()
    for i = 1, 3 do
        print("Task B: step " .. i)
        coroutine.yield()
    end
end)

scheduler:run()
-- Task A: step 1
-- Task B: step 1
-- Task A: step 2
-- Task B: step 2
-- Task A: step 3
-- Task B: step 3

4. 协程管道 / Coroutine Pipeline

-- 管道:多个协程串联,每个做一步处理
local function filter(prod, fn)
    return coroutine.wrap(function()
        for item in prod do
            if fn(item) then
                coroutine.yield(item)
            end
        end
    end)
end

local function map(prod, fn)
    return coroutine.wrap(function()
        for item in prod do
            coroutine.yield(fn(item))
        end
    end)
end

local function numbers(n)
    return coroutine.wrap(function()
        for i = 1, n do
            coroutine.yield(i)
        end
    end)
end

-- 组合管道:numbers → filter(偶数) → map(平方)
local pipeline = map(
    filter(numbers(10), function(x) return x % 2 == 0 end),
    function(x) return x * x end
)

for v in pipeline do
    print(v)    -- 4, 16, 36, 64, 100
end

🔴 高级 / Advanced — 协程内部原理

1. 协程的栈帧 / Coroutine Stack Frame

协程A 运行中          协程B suspended
┌───────────────┐    ┌───────────────┐
│ Frame 2: bar  │    │ (保存的状态)   │
│ Frame 1: foo  │    │ Frame 2: baz  │
│ Frame 0: main │    │ Frame 1: run  │
│ PC: 5         │    │ PC: 3         │
└───────────────┘    └───────────────┘

resume(co_B) 后:
协程A suspended      协程B 运行中
┌───────────────┐    ┌───────────────┐
│ (保存的状态)   │    │ Frame 2: baz  │
│ Frame 2: bar  │    │ Frame 1: run  │
│ Frame 1: foo  │    │ Frame 0: main │
│ Frame 0: main │    │ PC: 4         │
│ PC: 5         │    └───────────────┘
└───────────────┘

每个协程拥有独立的调用栈、局部变量和程序计数器(PC)。

2. 非对称协程 vs 对称协程 / Asymmetric vs Symmetric

-- Lua 使用非对称协程(asymmetric coroutines)
-- - resume/yield 是配对使用的
-- - yield 总是返回到调用 resume 的地方
-- - 结构清晰,不会"迷路"

-- 对称协程(如 Go 的 goroutine):
-- - 任意协程可以跳转到另一个协程
-- - 需要调度器管理
-- - 更灵活但更复杂

-- Lua 的非对称模型:
-- co1: resume(co2) ──────────────────────┐
--                                        ▼
-- co2: ──── do something ──── yield() ───┘
--                                        │
-- co1: ◄──────────────────────────────────┘
--      拿到 yield 的返回值,继续执行

3. resume 的错误处理 / Error Handling in resume

-- resume 总是捕获协程中的错误(类似 pcall)
local co = coroutine.create(function()
    error("Something went wrong!")
end)

local ok, err = coroutine.resume(co)
print(ok)      -- false
print(err)     -- stdin:2: Something went wrong!

-- 协程中的 error 不会传播到外层
-- 这是协程的一个重要安全特性

-- 但注意:如果 yield 后外层调用出错,error 会传播

4. 协程 vs 其他并发模型

模型抢占式并行内存开销适用场景
Lua 协程极低顺序逻辑拆分、生成器、状态机
Go goroutine~2KB高并发 I/O
Python asyncioI/O 密集型
OS 线程~1MBCPU 密集型

小结 / Summary

层级你需要知道的 / What You Need to Know
🟢 基础coroutine.create/resume/yield/status/wrap、状态:suspended/running/dead
🟡 进阶生产者-消费者、状态机、协作式调度器、管道模式
🔴 高级独立栈帧、非对称协程模型、错误隔离、与其他并发模型对比

下一章:错误处理 / Error Handling