lua_newstate vs lua_newthread

5 c++ lua multithreading

我正在尝试将lua实现到我现有的多线程应用程序中.我听说lua不是线程安全的我lua_State(s)为不同的线程创建了不同的东西.

浏览我发现的lua头文件lua_newthread.您将如何将其实现为一个全部就绪的线程应用程序.您是创建一个lua_State并创建单独的lua_newthread(s)还是会导致其他问题?

lhf*_*lhf 8

lua_newstate创造了一个新的Lua状态.不同的国家是完全分开的.

lua_newthread创建一个附加到给定Lua状态的新Lua线程.Lua状态可以在Lua VM中有多个执行线程,但它们不会同时执行; 它们是协同程序,可以共享数据..

不要将Lua线程与操作系统线程混淆.


mic*_*yer 8

注意,Lua线程不是操作系统线程(尽管有误导性名称),只有Lua本身(不在主机应用程序中)以异步方式执行代码.

所以答案是:lua_State在应用程序中为每个线程创建一个,如果需要在状态之间传递数据,则使用序列化库作为中间件.

  • 它不是真正的异步; 只有一个"线程"(通常称为[coroutines](http://www.lua.org/manual/5.1/manual.html#2.11))可以在任何时候运行,并且必须使用`明确地进行协程切换. coroutine.yield`. (3认同)

Nat*_*ebe 5

到目前为止的答案并没有真正提供实现 lua 块并行执行的各种方法的概述,所以这是我关于“多线程”lua 主题的 2 美分:

您基本上可以将 lua 状态/引擎分为两部分。首先是全局环境,其中包括lua状态的所有全局变量等。其次是执行堆栈,它会随着函数调用函数和程序执行而扩展和收缩。据我所知,实现多个lua流并行执行的方式有以下三种:

  1. 最简单的是协程(这似乎是 lhf 的答案所谈论的),可以使用协程库在纯 lua 中实现。如果您熟悉协作多任务处理,那么每个执行流都需要定期将处理器(或“产量”)明确地移交给其他执行流。协程共享单个环境和全局变量空间,但每个协程都有一个单独的执行堆栈。因此,通过这种方式,您可以有效地拥有 2 个以上同时处于活动状态的 lua 执行流,当然,尽管只有一个在给定的时刻执行,因为只有一个 OS 线程在执行这项工作。

优点:使用纯 lua 实现简单(无需更改 C 代码)

缺点:不得不到处放置 yield 调用的不便使代码看起来很糟糕。此外,更长的任务需要分解成小块,如果你不小心,如果你在 yield 调用之间在协程中执行更长的工作块,可能会导致糟糕的线程延迟。如果您调用了长时间运行的 C 函数,则无法在运行中产生结果,因此您很不走运,所有其他 lua 都必须等待。

  1. 另一个极端(如 michaelmeyer 所建议的)是拥有多个操作系统线程,每个线程都有一个专用的 lua 状态(由 lua_newstate() 返回)。这允许真正的抢先式多任务处理,包括在 C 或纯 lua 代码中将实时 lua 任务优先于其他长时间运行的任务。在这种情况下,每个 OS 线程都有自己的环境和全局变量空间,以及自己的执行堆栈。此功能也可以在不修改现有 lua 代码的情况下实现(除了创建 OS 线程并为每个线程调用 lua_newstate()),并且单独的 lua 引擎/状态彼此之间没有直接交互,因此同步/互斥不是需要保护lua环境。但困难的部分是跨不同线程共享数据,因为每个线程都有一个全新的全局变量空间。

优点:在股票 lua 代码上易于实现,并且可以充分利用无限数量的 CPU 内核

缺点:在线程/状态之间共享变量和数据需要多种聪明的策略之一,这种共享通常需要每次从 lua 访问共享变量时显式的 lua 代码,以及序列化和同步上的一些浪费或空闲的 CPU 周期. 它还使用了更多的内存,这在嵌入式系统上可能变得非常重要:我从事的一个项目每个 OS 线程只需要大约 100kB 的堆栈空间,但每个新的 lua 环境使用额外的 1.5MB一旦它加载了所有绑定库和全局上下文。

  1. 一个很好的折衷方案是使用 lua_newthread() 从现有状态中产生一个新状态,这将为您提供一个新创建的执行堆栈,但将与之前的 lua 状态共享现有环境和全局变量空间。然后,您可以继续在不同的 OS 线程上使用新创建的状态,从而允许抢占式多线程处理和实时任务的优先级排序,并具有更可预测的最坏情况延迟。但是……在这种情况下,您必须!在 C 中实现 lua_lock() 和 lua_unlock() 函数,以在共享 lua 环境上提供互斥保护。在幕后,lua VM 不断调用这些函数(默认情况下定义为什么都不做)来锁定和解锁环境,以防止其他操作系统线程损坏。这种方式的巨大优势在于,各个线程可以针对同一个全局变量空间同时运行lua,真正做到并行执行。当通过 lua 绑定调用 C 函数时,这是在解锁环境的情况下完成的,因此只要大部分计算成本高的工作在这些 C 函数中完成,您甚至可以使大量 CPU 内核饱和。这会导致更简单的编程流程,因为更长的操作(例如绘图调用、文件系统访问、2D/3D 图形操作、设备驱动程序访问等)不再需要复杂的回调序列以避免占用整个 lua 引擎并消除用 lua 编写的实时任务的延迟,就像应用程序的其余部分一样。当通过 lua 绑定调用 C 函数时,这是在解锁环境的情况下完成的,因此只要大部分计算成本高的工作在这些 C 函数中完成,您甚至可以使大量 CPU 内核饱和。这会导致更简单的编程流程,因为更长的操作(例如绘图调用、文件系统访问、2D/3D 图形操作、设备驱动程序访问等)不再需要复杂的回调序列以避免占用整个 lua 引擎并消除用 lua 编写的实时任务的延迟,就像应用程序的其余部分一样。当通过 lua 绑定调用 C 函数时,这是在解锁环境的情况下完成的,因此只要大部分计算成本高的工作在这些 C 函数中完成,您甚至可以使大量 CPU 内核饱和。这会导致更简单的编程流程,因为更长的操作(例如绘图调用、文件系统访问、2D/3D 图形操作、设备驱动程序访问等)不再需要复杂的回调序列以避免占用整个 lua 引擎并消除用 lua 编写的实时任务的延迟,就像应用程序的其余部分一样。

优点:数据共享要简单得多,而且几乎没有 CPU 开销(尽管您仍然需要在读取时注意其他可能正在写入变量的线程,例如在另一个线程添加到表时迭代表。 )

缺点:在 lua 执行过程中大量调用互斥锁会产生少量开销(在我的情况下,这会导致大约 6% 的 CPU 开销 - 有点打击,但值得付出代价)。此外,如果您试图在多个内核之间传播大量纯 lua 工作,由于对 lua 环境的共享访问,您最终会遇到瓶颈 - 尽管根据我的经验,您通常不会花费大部分时间CPU 周期运行纯 lua。您通常将大部分 CPU 周期花费在由 lua 调用的 C 函数中,在此期间您不拥有对环境的锁定,这不会阻止代码在其他内核上运行。