协程是什么

可以主动暂停之后从断点恢复的函数。不是线程——它在单线程内手动切换。

local co = coroutine.create(function(a, b)
    print("start", a, b)
    local c = coroutine.yield(a + b)    -- 暂停,返回 a+b 给外面
    print("resumed with", c)
    return "done"
end)

print(coroutine.resume(co, 1, 2))      -- start 1 2  →  true 3
print(coroutine.resume(co, 99))        -- resumed with 99 → true done
print(coroutine.resume(co))            -- 已死,返回 false

三个核心函数

作用
coroutine.create(fn) 创建协程对象(不立即跑)
coroutine.resume(co, args) 启动 / 恢复运行
coroutine.yield(values) 协程内部:暂停 + 返回

resume 返回 true, yielded_values...;如果协程出错 false, errmsg

4 个状态

local co = coroutine.create(function() coroutine.yield() end)

print(coroutine.status(co))     -- "suspended"  刚创建 / yield 后
coroutine.resume(co)
print(coroutine.status(co))     -- "suspended"  yield 了
coroutine.resume(co)
print(coroutine.status(co))     -- "dead"      跑完了

"running" 状态只能从协程内部看到(调 coroutine.status(coroutine.running()))。

用途 1:生成器

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

for v in range(5) do print(v) end   -- 1 2 3 4 5

coroutine.wrap(fn) 返回一个每次调用就 resume 一次的函数——配合 for 写迭代器超清爽。

用途 2:异步 I/O 风格化

local function fake_async()
    local ok, data = coroutine.yield("read", "file.txt")
    return data
end

-- 调度器
local co = coroutine.create(fake_async)
local ok, op, arg = coroutine.resume(co)
-- 假设这里去做真异步读,完成后...
coroutine.resume(co, true, "file contents")

OpenResty / cqueues / luv 等异步框架背后就是这种调度。

用途 3:状态机

local function door()
    while true do
        coroutine.yield("closed")
        coroutine.yield("opening")
        coroutine.yield("open")
        coroutine.yield("closing")
    end
end

local co = coroutine.create(door)
for i = 1, 6 do
    local _, state = coroutine.resume(co)
    print(state)
end
-- closed / opening / open / closing / closed / opening

yield 不能跨 C 边界

在 C 函数(如 pcallxpcall、回调进 C 库再调 Lua)里 yield 会失败:

attempt to yield across C-call boundary

5.2+ 有 coroutine.isyieldable() 判断能不能 yield,以及 pcall 在 5.2 起改成 yieldable。Lua 5.1 / LuaJIT 仍受此限。

协程不并行

-- 错觉:以为这是并发
local co1 = coroutine.create(work1)
local co2 = coroutine.create(work2)
coroutine.resume(co1)
coroutine.resume(co2)

一次只有一个在跑——协程是协作式调度,不是抢占式线程。要并行需要 lua-laneseffil 多 Lua 状态机方案。

心智模型

把协程当成可以暂停的函数。它的核心价值是把"看起来同步、其实在等待"的代码写得像普通顺序代码——而不是把一堆回调嵌起来。

-- 没有协程:
fetch("/a", function(a)
    fetch("/b", function(b)
        fetch("/c", function(c)
            print(a, b, c)
        end)
    end)
end)

-- 有协程:
local a = await(fetch("/a"))
local b = await(fetch("/b"))
local c = await(fetch("/c"))
print(a, b, c)

其中 await 通常实现为 coroutine.yield,调度器负责异步完成时 resume

→ 下一篇 string 库