C++ 与 Lua 的协程交互

本文介绍了Lua不支持抢占式线程但支持协程,通过C++ API如lua_newthread、lua_resume、lua_yieldk等实现线程和协程的交互。重点讲述了如何在C++中创建Lua线程,使用lua_resume恢复和挂起协程,以及lua_xmove在不同线程间交换数据,强调了在多线程环境中管理Lua状态的重要性。
摘要由CSDN通过智能技术生成

零、前言

Lua 语言不支持真正的多线程,即不支持共享内存的抢占式线程。

这样的模式能减少一些多线程的问题。多线程的问题源于线程抢占和共享内存,而如果非抢占式线程或者不使用共享内存则能避免多线程问题,Lua 同时支持这两种方案。从之前分享的《Lua 协程》文章中知道:

  1. Lua 语言的线程是协作式的,即协程,可以避免因不可预知的线程切换带来的问题。
  2. Lua 状态间内存不共享,所以各个状态相互独立运行,可以并行操作。

一、多线程

从 C-API 的角度,可以把线程当作一个栈,每个栈保存着一个线程中挂起的函数调用信息,以及每个函数调用的参数和局部变量。也就是说,一个栈包括了一个线程得以继续运行所需的所有信息。 因此,要达到多线程就需要多个栈。

每当创建一个 lua_State 时,Lua 就会自动用这个 lua_State 创建一个主线程。这个主线程永远不会被垃圾回收,只有当调用 lua_close 关闭时才会释放。

可以通过 C-API lua_newthread 在已有的 lua_State 中创建新的线程。

二、lua_newthread

lua_State *(lua_newthread) (lua_State *L);

描述:

这个函数会将新线程 thread 类型的值压入栈中,并返回一个表示该线程的 lua_State 类型的指针。

新线程和旧线程的异同点:

可以使用以下代码创建了一个新的线程,并且得到一个新的 lua_State 指针类型的 L1 值。

lua_State *L1 = lua_newthread(L);
  1. 新线程 L1 与主线程 L 共享全局变量和注册表,但是它们具有独立的栈空间。新线程 L1 从空栈开始,主线程 L 则在其栈顶会引用 L1 这个新线程。
  2. 除了主线程 L 需要使用 lua_close 进行关闭,新线程 L1 和其他的 Lua 对象一样都是垃圾回收的对象。所以永远不要使用未被正确锚定在 lua_State 中的线程(主线程是内部锚定,因此不用担心会被回收),因为所有对 Lua API 的调用都有可能回收未锚定的线程,即使是正在使用这个线程进行函数调用。 要避免这种情况,应该在 “已锚定的线程的栈”、“注册表”、“Lua 变量” 中保留对使用中线程的引用,防止被垃圾回收机制回收。
  3. 拥有的新线程 L1 可以像主线程 L 一样使用,进行压栈调用函数等操作。

举个例子:

  1. 创建一个新的线程。
  2. 分别打印各自栈的内容。
lua_State *L = luaL_newstate();
luaL_openlibs(L);

// 用 L 创建一个新的线程
// L 和 L1 各自有一个栈
// L 的栈顶是 L1 的 thread
// L1 的栈是空
lua_State *L1 = lua_newthread(L);

printf("主线程 L 栈深度:%d\n", lua_gettop(L));
printf("主线程 L 栈内容:------------\n");
stackDump(L);

printf("新线程 L1 栈深度:%d\n", lua_gettop(L1));
printf("新线程 L1 栈内容:------------\n");
stackDump(L1);

lua_close(L);

输出如下:

主线程 L 栈深度:1
------------ 主线程 L 栈内容:------------
栈顶
^ typename: thread, value: thread    
栈底

新线程 L1 栈深度:0
------------ 新线程 L1 栈内容:------------
栈顶
栈底

三、新线程的作用

如果创建一个新的线程,只是用来运行简单的函数,这种场景其实只需要在主线程中执行即可。

创建新线程的主要目的是运行协程。 可以在新线程中挂起协程,然后继续运行其他线程的 Lua 代码,在需要的节点恢复协程,使新线程继续执行挂起点之后的逻辑代码,而运行协程需要用到 lua_resume C-API 。

四、lua_resume

LUA_API int  (lua_resume)     (lua_State *L, lua_State *from, int narg,
                               int *nres);

描述:

使用 lua_resume 和使用 lua_pcall 调用函数很相似,压入协程的参数,然后将待调用函数(协程体)压入栈,并以参数的数量作为参数 narg 调用 lua_resume

lua_resumelua_pcall 的不同点:

  1. lua_resume 中没有表示期望结果数量的参数,总是返回被调用函数的所有结果。
  2. 没有错误处理函数的参数,发生错误时不会进行栈展开,可以在错误发生后检查栈的情况。
  3. 如果正在运行的函数被挂起,lua_resume 就会返回代码 LUA_YIELD ,并将线程置于一个后续可以恢复执行的状态中。

参数:

  • 参数 L:Lua State 的指针。
  • 参数 from:正在执行调用的线程,或为 NULL 。
  • 参数 narg:入参参数数量。
  • 参数 nres:协程体返回的值数量。

返回值:

返回当前协程体处于哪个状态:

  1. 如果被挂起,则返回 LUA_YIELD
  2. 如果已经执行完成&#x
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值