Lua 协同程序(coroutine)

Lua 协同程序(coroutine)讲解

关键点

  • Lua 协同程序是一种实现协作式多任务的机制,允许多个函数在不使用线程的情况下并发运行。
  • 每个协同程序有自己的堆栈、局部变量和指令指针,但共享全局变量和其他资源。
  • 研究表明,协同程序适合实现迭代器、状态机和异步操作的同步处理,广泛用于游戏开发和嵌入式系统。

什么是 Lua 协同程序?

Lua 协同程序(coroutine)是一种特殊的函数,可以暂停和恢复执行,类似于线程,但它们是非抢占式的,同一时刻只有一个协同程序在运行。它们适合实现轻量级的并发任务,例如处理异步操作或生成序列。

如何使用协同程序?

Lua 提供 coroutine 库,支持创建、启动、挂起和恢复协同程序。主要函数包括:

  • coroutine.create(f):创建新协同程序,返回一个线程对象。
  • coroutine.resume(co, ...):启动或继续执行协同程序。
  • coroutine.yield(...):挂起当前协同程序,返回指定值。
  • coroutine.status(co):查看协同程序状态(如运行、挂起或已结束)。

示例

以下是一个简单示例,展示协同程序的创建和使用:

function foo(a)
    print("foo", a)
    return coroutine.yield(2 * a)
end

co = coroutine.create(function(a, b)
    print("co-body", a, b)
    local r = foo(a + 1)
    print("co-body", r)
    local r, s = coroutine.yield(a + b, a - b)
    print("co-body", r, s)
    return b, "end"
end)

print("main", coroutine.resume(co, 1, 10))
print("main", coroutine.resume(co, "r"))
print("main", coroutine.resume(co, "x", "y"))
print("main", coroutine.resume(co, "x", "y"))

输出结果:

co-body    1   10
foo    2
main    true    4
co-body    r
main    true    11  -9
co-body    x   y
main    false   10  end
main    false   cannot resume dead coroutine

更多资源


Lua 协同程序(coroutine)详细分析

Lua 是一种轻量级脚本语言,因其简洁和灵活性而广泛应用于游戏开发、嵌入式系统等领域。协同程序(coroutine)是 Lua 中一个核心特性,用于实现协作式多任务处理,允许多个函数在不使用线程的情况下并发运行。本文基于网络资源(如菜鸟教程、CSDN 博客、知乎等)进行详细分析,旨在为用户提供全面的中文讲解,时间为 2025 年 8 月 4 日上午 9:37 HKT。

引言

协同程序是一种程序控制机制,早在 1963 年就被提出,用于实现协作式的多任务处理。与线程不同,协同程序是非抢占式的,同一时刻只有一个协同程序在运行,且必须显式挂起才能切换到其他协同程序。Lua 实现的是一种非对称式或半对称式协同程序,通过 resumeyield 实现程序控制权的传递。

协同程序的定义与特性

根据菜鸟教程和知乎的描述,Lua 协同程序具有以下特性:

  • 定义:协同程序是一种可以暂停和恢复执行的特殊函数,类似于线程,但它们不是操作系统的线程,而是 Lua 内部模拟的执行流。
  • 特性
  • 每个协同程序有自己的堆栈、局部变量和指令指针。
  • 协同程序共享全局变量和其他大部分资源。
  • 协同程序是非抢占式的,即只有在显式调用 yield 时才会挂起,当前运行的协同程序必须主动放弃执行权。
  • 协同程序的创建和切换开销较小,适合轻量级并发任务。
  • 与线程的区别:线程可以并发执行,多个线程可能同时运行;协同程序则需要协作运行,同一时刻只有一个在执行,适合避免多线程的锁机制和竞争条件。

协同程序的主要函数

Lua 将所有与协同程序相关的函数封装在 coroutine 表中,根据简书和 CSDN 博客的描述,常用函数包括:

函数名描述
coroutine.create(f)创建一个新协同程序,其主体函数为 f,返回一个线程对象。
coroutine.resume(co [, val1, ...])启动或继续执行协同程序 co,参数作为主体函数的输入。返回 trueyield 的返回值,或 false 和错误信息。
coroutine.yield(...)挂起当前协同程序的执行,返回指定的值。
coroutine.status(co)返回协同程序 co 的状态:"running""suspended""normal"(主线程)或 "dead"
coroutine.isyieldable()如果当前协同程序可以挂起,返回 true。主线程和 C 函数中不可挂起。
coroutine.running()返回当前运行的协同程序对象,若在主线程中返回 nil

协同程序的状态

根据博客园和 CSDN 的描述,协同程序有以下四种状态:

  • suspended(挂起):刚创建的协同程序或通过 yield 挂起的协同程序处于此状态。
  • running(运行):当前正在执行的协同程序。
  • dead(已结束):协同程序执行完毕或发生错误后进入此状态。
  • normal(正常):主线程的状态,通常不直接用于协同程序。

可以通过 coroutine.status(co) 查看状态,例如:

co = coroutine.create(function() end)
print(coroutine.status(co))  -- 输出:suspended

协同程序的工作原理

根据知乎和电子蓝的博客,协同程序的工作原理如下:

  • 当调用 coroutine.create(f) 时,创建一个新的协同程序对象,初始状态为挂起(suspended),不会立即执行。
  • 调用 coroutine.resume(co, val1, ...) 时,启动或恢复协同程序的执行,参数 val1, ... 作为主体函数的输入。如果是首次启动,主体函数从头开始执行;如果是恢复,则从上次 yield 的位置继续。
  • 在协同程序内部调用 coroutine.yield(val1, ...) 时,挂起当前协同程序的执行,并将 val1, ... 返回给 resumeresume 返回 true 和这些值。
  • 如果协同程序执行完毕(返回或出错),状态变为 dead,尝试再次 resume 会失败,返回 false 和错误信息。

示例与分析

以下是几个典型示例,展示了协同程序的使用:

  1. 简单协同程序示例
   function foo(a)
       print("foo", a)
       return coroutine.yield(2 * a)
   end

   co = coroutine.create(function(a, b)
       print("co-body", a, b)
       local r = foo(a + 1)
       print("co-body", r)
       local r, s = coroutine.yield(a + b, a - b)
       print("co-body", r, s)
       return b, "end"
   end)

   print("main", coroutine.resume(co, 1, 10))
   print("main", coroutine.resume(co, "r"))
   print("main", coroutine.resume(co, "x", "y"))
   print("main", coroutine.resume(co, "x", "y"))

输出结果如前所述,展示了 resumeyield 的交互。

  1. 生产者-消费者问题:根据菜鸟教程,协同程序可用于实现经典的生产者-消费者模型:
   function producer()
       while true do
           local x = io.read()  -- 读取输入
           coroutine.yield(x)   -- 挂起并返回输入
       end
   end

   function consumer(prod)
       while true do
           local x = coroutine.resume(prod)  -- 恢复生产者
           print(x)  -- 打印生产者的输出
       end
   end

   p = coroutine.create(producer)
   consumer(p)

此例中,生产者读取输入并挂起,消费者恢复生产者并处理输出,实现了协作式数据流。

应用场景

根据阿里云开发者社区和知乎的描述,协同程序在 Lua 中的常见应用包括:

  • 迭代器:实现自定义迭代器,例如生成斐波那契数列或处理大型数据集。
  • 状态机:适合实现复杂的状态转换逻辑,如游戏中的 AI 行为。
  • 异步操作的同步处理:将异步代码改写为同步形式,提高代码可读性,例如网络请求或文件 I/O。

例如,生成器(generator)示例:

function numbers()
    local i = 0
    return function()
        i = i + 1
        return i
    end
end

local gen = numbers()
print(gen())  -- 输出:1
print(gen())  -- 输出:2
print(gen())  -- 输出:3

注意事项与性能

  • 性能考虑:协同程序的切换开销较小,但频繁的 resumeyield 可能影响性能,建议在需要暂停和恢复的任务中使用。
  • 状态管理:需注意协同程序的状态,避免尝试恢复已结束(dead)的协同程序。
  • C 函数限制:主线程和 C 函数中不可调用 yield,需确保协同程序在 Lua 环境中挂起。

版权与参考资料

本文内容参考了以下资源,版权归原作者所有:

版权声明:部分内容受版权保护,引用时请遵守相关规定。

结论

Lua 协同程序是一种强大的特性,适合实现轻量级的并发任务,通过 resumeyield 实现协作式多任务处理。广泛应用于迭代器、状态机和异步操作的同步处理,是 Lua 语言中独特的优势之一。本文提供的示例和表格应能帮助用户更好地理解和应用 Lua 协同程序。

类似文章

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注