前置知识: Lua

协程详解

2 minIntermediate2026/6/14

Lua协程与多任务

概述

Lua 的协程(coroutine)是一种非抢占式的多任务机制,通过协作式调度实现并发。协程可以暂停执行(yield)和恢复执行(resume),非常适合实现状态机、生成器和协作式多任务。

基础概念

协程核心 API

函数说明
coroutine.create创建协程,返回 thread
coroutine.resume恢复协程执行
coroutine.yield挂起当前协程
coroutine.wrap创建协程,返回可调用的函数
coroutine.status获取协程状态
coroutine.running获取当前运行的协程

协程状态

状态说明
suspended挂起,等待 resume
running正在执行
normal恢复了其他协程
dead执行完毕或出错

快速上手

创建和运行协程

-- 方式一:create + resume
local co = coroutine.create(function()
  for i = 1, 5 do
    print("协程输出: " .. i)
    coroutine.yield(i)
  end
end)

-- resume 返回:是否成功, yield 传入的值
local ok, value = coroutine.resume(co) -- true 1
print(coroutine.status(co))            -- suspended

-- 继续执行
ok, value = coroutine.resume(co)       -- true 2

-- 方式二:wrap(更简洁)
local co2 = coroutine.wrap(function()
  for i = 1, 5 do
    coroutine.yield(i)
  end
end)

print(co2()) -- 1(直接调用,不需要处理 ok)
print(co2()) -- 2

resume 和 yield 的数据传递

-- resume 向 yield 传值
local co = coroutine.create(function()
  local received = coroutine.yield("hello") -- 第一次 yield
  print("收到: " .. received)               -- 输出 "收到: world"
  return "done"
end)

local ok, msg = coroutine.resume(co)  -- msg = "hello"
coroutine.resume(co, "world")         -- 传递 "world" 给 yield

-- yield 向 resume 传值
local co2 = coroutine.create(function(a, b) -- resume 传入的参数
  coroutine.yield(a + b)                    -- yield 返回 a+b
  coroutine.yield(a * b)                    -- yield 返回 a*b
end)

local ok, result = coroutine.resume(co2, 3, 4) -- result = 7
ok, result = coroutine.resume(co2)              -- result = 12

详细用法

生产者-消费者模式

-- 生产者
function producer()
  return coroutine.wrap(function()
    while true do
      local value = io.read()
      if not value then return end
      coroutine.yield(value)
    end
  end)
end

-- 消费者
function consumer(prod)
  for value in prod do
    print("消费: " .. value)
  end
end

-- 过滤器
function filter(prod, predicate)
  return coroutine.wrap(function()
    for value in prod do
      if predicate(value) then
        coroutine.yield(value)
      end
    end
  end)
end

-- 使用管道
local prod = producer()
local filtered = filter(prod, function(v) return #v > 3 end)
consumer(filtered)

状态机

-- 使用协程实现状态机
function trafficLight()
  return coroutine.wrap(function()
    local state = "red"
    local durations = { red = 5, green = 4, yellow = 1 }
    local transitions = { red = "green", green = "yellow", yellow = "red" }

    while true do
      coroutine.yield(state, durations[state])
      state = transitions[state]
    end
  end)
end

local light = trafficLight()
for i = 1, 10 do
  local state, duration = light()
  print(string.format("信号灯: %s (持续 %d 秒)", state, duration))
end

协程实现异步回调

-- 将回调式 API 转换为同步式调用
function asyncToSync(asyncFn, ...)
  local co = coroutine.running()
  local args = { ... }

  asyncFn(unpack(args), function(result)
    coroutine.resume(co, result)
  end)

  return coroutine.yield()
end

-- 使用
function fetchData(url, callback)
  -- 模拟异步操作
  setTimeout(function()
    callback("数据来自: " .. url)
  end, 1000)
end

-- 同步式调用
local co = coroutine.wrap(function()
  local data = asyncToSync(fetchData, "https://example.com")
  print(data)
end)
co()

常见场景

生成器模式

-- 斐波那契数列生成器
function fibonacci()
  return coroutine.wrap(function()
    local a, b = 0, 1
    while true do
      coroutine.yield(a)
      a, b = b, a + b
    end
  end)
end

local fib = fibonacci()
for i = 1, 10 do
  print(fib()) -- 0, 1, 1, 2, 3, 5, 8, 13, 21, 34
end

-- 素数生成器
function primes()
  return coroutine.wrap(function()
    local n = 2
    while true do
      local isPrime = true
      for i = 2, math.floor(math.sqrt(n)) do
        if n % i == 0 then isPrime = false; break end
      end
      if isPrime then coroutine.yield(n) end
      n = n + 1
    end
  end)
end

local p = primes()
for i = 1, 10 do print(p()) end

任务调度器

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

function scheduler.spawn(fn)
  local co = coroutine.wrap(fn)
  table.insert(scheduler.tasks, co)
end

function scheduler.yield()
  coroutine.yield()
end

function scheduler.run()
  while #scheduler.tasks > 0 do
    local i = 1
    while i <= #scheduler.tasks do
      local co = scheduler.tasks[i]
      if coroutine.status(co) == "dead" then
        table.remove(scheduler.tasks, i)
      else
        co()
        i = i + 1
      end
    end
  end
end

-- 使用
scheduler.spawn(function()
  for i = 1, 3 do
    print("任务A: " .. i)
    scheduler.yield()
  end
end)

scheduler.spawn(function()
  for i = 1, 3 do
    print("任务B: " .. i)
    scheduler.yield()
  end
end)

scheduler.run()
-- 交替输出: A1, B1, A2, B2, A3, B3

注意事项

  • Lua 协程是非抢占式的,必须主动 yield 才能切换
  • resume 只能在主线程中调用,不能在协程中 resume 另一个非 suspended 的协程
  • 协程出错时 resume 返回 false 和错误信息
  • wrap 创建的协程出错时会直接抛出异常
  • 协程不是线程,不提供真正的并行执行
  • 避免在协程中执行长时间不 yield 的操作,会阻塞整个程序

进阶用法

协程

-- 协程池:复用协程对象
local Pool = {}
Pool.__index = Pool

function Pool.new()
  return setmetatable({ workers = {} }, Pool)
end

function Pool:submit(fn)
  local co = table.remove(self.workers) or coroutine.create(function(task)
    -- 循环执行任务
    while task do
      task()
      task = coroutine.yield() -- 执行完毕,等待下一个任务
    end
  end)
  coroutine.resume(co, fn) -- 传入任务
  table.insert(self.workers, co) -- 放回池中
end

-- 使用
local pool = Pool.new()
pool:submit(function() print("任务1") end)
pool:submit(function() print("任务2") end)