返回首页

教程导航

返回首页

🏷️ 标签(1)

🗂️ 分类(1)

第 13 章 协程

第 13 章 协程

协程允许函数在保留当前状态的同时暂停和恢复。它适合生成器、状态机、流式处理和需要把复杂异步流程写成顺 code 的场景。

协程模型

Lua 协程是协作式的:只有显式 yield 或结束时才会切换,不会像操作系统线程一样被抢占。协程之间默认不并行,也不会自动利用多核 CPU。

一个协程从 suspended 状态开始,经过 resume 运行,在 yield 处暂停,最终进入 dead 状态。

create、resume 与 yield

local worker = coroutine.create(function(name)
  local value = coroutine.yield("hello " .. name)
  print("收到:", value)
  return "done"
end)

print(coroutine.status(worker)) -- suspended

local ok, message = coroutine.resume(worker, "Lua")
print(ok, message) -- true hello Lua

local finished, result = coroutine.resume(worker, "继续")
print(finished, result) -- true done
print(coroutine.status(worker)) -- dead

resume 返回是否成功,以及 yield 的值或最终返回值。yield 返回下一次 resume 传入的参数。

wrap

coroutine.wrap 返回一个普通函数,调用时自动 resume。错误会直接向调用方抛出:

local generate = coroutine.wrap(function()
  for index = 1, 3 do
    coroutine.yield(index * 10)
  end
end)

print(generate()) -- 10
print(generate()) -- 20
print(generate()) -- 30

需要显式处理错误时使用 create 和 resume;生成器场景使用 wrap 更简洁。

状态与关闭

print(coroutine.isyieldable())

local running, isMain = coroutine.running()
print(type(running), isMain)

Lua 5.4 提供 coroutine.close。关闭一个挂起或正常结束的协程,会执行它的待关闭资源:

local function task()
  local handle <close> = setmetatable({}, {
    __close = function()
      print("清理资源")
    end
  })
  coroutine.yield()
end

local coroutineValue = coroutine.create(task)
coroutine.resume(coroutineValue)
print(coroutine.close(coroutineValue)) -- true

不能关闭正在运行的协程。close 也可能返回失败和错误对象。

生产者与消费者

协程可以把“逐个产生值”的逻辑写成普通循环:

local function producer(items)
  return coroutine.create(function()
    for _, item in ipairs(items) do
      coroutine.yield(item)
    end
  end)
end

local source = producer({ "a", "b", "c" })

while true do
  local ok, item = coroutine.resume(source)
  if not ok then
    error(item)
  end
  if item == nil then
    break
  end
  print("消费:", item)
end

如果数据源是文件,可以在协程中逐行读取,消费者只看到值序列。这样读取细节与业务处理可以分离。

双向通信

resume 和 yield 都能传值:

local processor = coroutine.create(function()
  while true do
    local input = coroutine.yield()
    if input == nil then
      break
    end
    print("处理:", input:upper())
  end
end)

coroutine.resume(processor)
coroutine.resume(processor, "lua")
coroutine.resume(processor, "coroutine")
coroutine.resume(processor)

第一次 resume 只启动协程,直到遇到 yield。之后的参数才成为挂起点 yield 的返回值。

状态机与异步流程

协程可以让状态机按顺序表达:

local function workflow()
  print("1. 读取输入")
  local input = coroutine.yield("waiting-input")

  print("2. 校验输入")
  if not input or input == "" then
    return nil, "empty input"
  end

  print("3. 保存结果")
  return "saved"
end

local process = coroutine.create(workflow)
print(coroutine.resume(process))
print(coroutine.resume(process, "hello"))

在事件驱动程序中,调度器可以在 I/O 完成时恢复对应协程。这能把回调链写成顺序代码,但调度器仍需处理超时、取消、并发上限和异常。

简单协程调度

local jobs = {}

local function schedule(job)
  jobs[#jobs + 1] = coroutine.create(job)
end

local function run()
  while #jobs > 0 do
    local job = table.remove(jobs, 1)
    local ok, message = coroutine.resume(job)

    if not ok then
      print("job failed:", message)
    elseif coroutine.status(job) ~= "dead" then
      jobs[#jobs + 1] = job
    end
  end
end

schedule(function()
  coroutine.yield()
  print("first")
end)

schedule(function()
  print("second")
end)

run()

这个调度器会空转,不能替代真正的 I/O 事件循环。生产程序应使用成熟的异步框架或宿主事件循环。

协程使用边界

  • 协程不是线程,不能自动并行执行 CPU 密集任务。

  • 不要在协程之间共享可变状态而没有明确同步策略。

  • 捕获错误并关闭未完成协程,避免资源泄漏。

  • 避免在库的每个函数中隐藏协程切换,调用者应能预测控制流。

  • 主线程中调用 coroutine.yield 会失败,除非当前函数处于可让出环境。

上一章:面向对象 Lua | 下一章:错误处理