Lua 的并发(Concurrency)设计核心在于其轻量级、嵌入式的哲学,以及对协作式多任务的首选。它通过强大的协程机制实现并发,但本身不提供多线程/多进程的并行能力。


多线程/多进程#

  • 核心语言无内置支持: Lua 语言本身的核心 VM 被设计为单线程执行。它不提供内置的语法或标准库来直接创建和管理线程(std::thread)或进程(fork)。
  • 独立的 Lua State: 一个 Lua VM 实例被称为一个“Lua State”。每个 Lua State 是完全独立的运行时环境,拥有自己的全局变量、栈、打开的文件、垃圾回收器等。它们之间默认不共享任何数据。
  • 宿主语言的责任: 如果需要在 Lua 中实现真正的并行(多核利用),必须依赖于宿主语言(如 C/C++)的多线程/多进程机制
    • 实现方式: 在宿主语言的每个线程或进程中,创建并运行一个独立的 Lua State
    • 数据交换: 这些独立的 Lua State 之间无法直接共享内存。数据交换必须通过宿主语言提供的进程间通信 (IPC) 或线程间通信 (ITC) 机制(如消息队列、共享内存、管道、套接字等)来完成。
    • 优点: 简单安全,因为 Lua State 之间是隔离的,避免了复杂的并发同步问题。
    • 缺点: 额外的通信开销和复杂性,且无法在单个 Lua State 内部实现并行。
  • 第三方库(封装): 存在一些第三方库(如 LuaLanes)试图提供在 Lua 中模拟多线程/多进程的 API。这些库通常是在底层创建独立的 Lua State,并封装了 IPC 机制,方便 Lua 开发者使用,但其本质仍然是基于宿主语言的底层能力和独立的 Lua State。

协程#

协程的设计与实现#

  • 设计理念: Lua 协程是为了提供协作式多任务 (Cooperative Multitasking) 而设计。它们允许在单个线程中实现任务的暂停和恢复,以模拟并发,而无需复杂的锁机制。
  • “有栈协程” (Stackful Coroutines): Lua 协程是有栈的。这里的“栈”指的不是操作系统的 C 语言栈,而是 Lua 虚拟机内部维护的Lua VM 栈
    • Lua VM 栈: 每个协程在创建时都会分配一个独立的 Lua VM 栈(或在需要时动态扩展)。这个栈存储着协程的局部变量、函数参数、中间表达式结果和函数调用上下文。
  • 实现机制:
    • coroutine.create(function) 创建一个新的协程(一个thread类型的值),但并不立即执行。它会分配并初始化一个新的 Lua VM 栈。
    • coroutine.yield(...) (保存栈): 当一个协程调用 yield 时,Lua VM 会:
      1. 保存当前 Lua VM 栈的完整状态(包括所有活跃的栈帧、局部变量值、程序计数器等)。这些信息会被存储在协程对象本身(在堆上分配)中。
      2. 暂停当前协程的执行。
      3. 将控制权返回给调用 coroutine.resume 的那个协程或主线程。 C 语言栈会正常展开,yield 作为一个 C 函数正常返回。
    • coroutine.resume(co, ...) (恢复栈): 当一个协程被 resume 时,Lua VM 会:
      1. 从协程对象中加载并恢复其之前保存的 Lua VM 栈状态。 这包括设置栈顶指针、恢复所有栈帧和程序计数器,使得协程能够从上次 yield 的点继续执行。
      2. 将控制权转移给被恢复的协程。 C 语言栈上会为 resume 函数创建一个新的栈帧,并在其中运行被恢复的 Lua 协程。
  • 优点: 简单、高效、避免了与 OS 栈相关的复杂性,并且由于是协作式的,没有竞态条件和锁的开销。
  • 缺点: 无法利用多核 CPU。

协程示例:

function long_computation()
    print("Start part 1")
    coroutine.yield("step1_data") -- 保存整个栈
    print("Start part 2")
    coroutine.yield("step2_data", "helloworld") -- 保存整个栈
    print("End computation")
    return "result"
end

local co = coroutine.create(long_computation)
local _, data1, d1 = coroutine.resume(co) -- 恢复栈,从 'Start part 1' 继续
print("Received:", data1, d1)
local _, data2, d2 = coroutine.resume(co) -- 恢复栈,从 'Start part 2' 继续
print("Received:", data2, d2)
local _, result = coroutine.resume(co) -- 恢复栈,从 'End computation' 继续
print("Result:", result)

输出:

Start part 1
Received:	step1_data	nil
Start part 2
Received:	step2_data	helloworld
End computation
Result:	result

注意,这里调用 consume 的接收参数并没有要求和 yield 中的传参数目保持一致。如果接收参数多,会用 nil 填充,如果传参多,多余的参数会被忽略。

非抢占式调度#

  • 非抢占式 (Cooperative Scheduling): Lua 协程是非抢占式调度的。
    • 这意味着一个协程一旦开始执行,除非它主动调用 coroutine.yield()coroutine.resume() 或遇到 error,否则它会一直运行,直到完成或阻塞。
    • Lua VM 不会在代码执行的任意点(例如,经过一段时间后)强制暂停一个正在运行的协程。
    • 优点: 编程模型相对简单,无需考虑复杂的同步问题和竞态条件,因为在任何给定时刻,只有一个协程的代码正在执行。
    • 缺点: 如果一个协程内部有长时间运行的计算循环而没有 yield,它会“霸占”CPU,导致其他协程无法执行,从而可能阻塞整个应用程序,影响响应性。程序员必须确保在适当的时机插入 yield 点。

应用场景#

  • 协作式调度: 协程的使用完全依赖于程序员显式地调用 yield 来交出控制权。这使得协程非常适合需要暂停等待某个事件(如 I/O 操作)的场景。
  • 生产者/消费者模式: 协程天然适合实现生产者/消费者模式,一个协程生产数据,另一个协程消费数据。
  • 迭代器: 可以用来创建复杂的迭代器,每次迭代都在 yield 处暂停并返回一个值。
  • 非阻塞 I/O (Pseudo-asynchronous I/O): 结合外部事件循环和 I/O 库,协程可以模拟非阻塞 I/O。当一个 I/O 操作启动时,协程 yield,事件循环可以运行其他协程。当 I/O 完成时,事件循环会 resume 相应的协程。

总结图:

                  +------------------------+
                  |  OS Thread (e.g., Main Thread) |
                  |  (Uses C-Stack for its own ops) |
                  +-----------+------------+
                              |
                              V
                  +--------------------------------+
                  |  Lua Interpreter/VM Global State |
                  |  (Single-threaded execution)   |
                  +-----------+------------+
                              |
 +--------------------------+ | +--------------------------+
 | Lua Coroutine Co1        | | | Lua Coroutine Co2        |
 | (Has its own Lua VM Stack)| | | (Has its own Lua VM Stack)|
 | +-----------+            | | +-----------+            |
 | | Lua Stack +----------->+---<+ Lua Stack |            |
 | | (on Heap) |            | | | (on Heap) |            |
 | +-----------+            | | +-----------+            |
 |   `yield()`: Save Stack, | |   `resume(Co1)`: Restore |
 |              Return Ctrl+| |                Stack, Run |
 +--------------------------+ | +--------------------------+
                              |
                              V
                  +--------------------------------+
                  | Concurrency: Achieved via       |
                  | Cooperative Multitasking        |
                  | (No Parallelism built-in)       |
                  +--------------------------------+

For Parallelism:
  - Spawn Multiple OS Threads/Processes (via Host Language).
  - Each OS Thread/Process gets its own independent Lua Interpreter/VM State.
  - Inter-thread/process communication handled by Host Language IPC.