环境表 |
|
在 Lua 5.0.2 中,Lua 闭包(但不是 C 闭包)具有环境表。环境表用于查找未绑定的名称(即“全局变量”)。当通过执行函数表达式或语句创建新的闭包时,新闭包会获取当前执行闭包的环境表,因此在没有更改的情况下,所有闭包共享同一个环境表,这使得它几乎与以前版本的 Lua 中的全局表相同。
可以使用 lua_getfenv()
(在 C 中)和 getfenv()
(在 Lua 中)访问闭包的环境表;可以使用 lua_setfenv()
/ setfenv()
将其设置为不同的表。有关这些函数的信息,请参阅手册。
在 5.0.2 中,C 闭包实际上共享一个全局表,该表与当前执行的线程相关联。C 闭包可以使用伪索引 LUA_GLOBALSINDEX
访问此表(实际上可以使用 lua_replace(L, LUA_GLOBALSINDEX)
修改它)。这很有用,但可能有点混乱。
在 5.1 中,每个 C 闭包都有自己的环境表引用。但是,LUA_GLOBALSINDEX
伪索引没有改变,因此引用 LUA_GLOBALSINDEX
的现有代码仍然会引用当前执行线程的“全局”表。(该表现在被称为线程的环境表,可以使用应用于线程对象本身的 lua_getfenv 和 lua_setfenv 访问和修改它。)为了访问闭包的环境表,可以使用 LUA_ENVIRONINDEX
;可以使用 lua_getfenv()
和 lua_setfenv()
(应用于闭包对象)访问和修改闭包环境表(对于 C 闭包或 Lua 闭包)。
您可以使用 lua_replace(L, LUA_ENVIRONINDEX)
替换“您自己的”环境,就像您可以使用 lua_replace(L, LUA_GLOBALSINDEX)
更改全局表一样。但是,更常见的情况是在新创建的闭包上调用 lua_setfenv()
。
(完整)用户数据也有环境表,尽管正如我在其他地方论证的那样(UserDataRefinement),这个名称令人困惑。用户数据环境表仅用于存储与用户数据相关的的信息;它们没有伪索引。
新创建的闭包(和用户数据)会从当前正在执行的闭包(如果有)获取其环境表,否则从当前线程获取。(再说一次,正如我在其他地方论证的那样,这对用户数据来说不是一个特别有用的默认值,但我不会再赘述。)
lua_pushcclosure()
在创建 C 闭包时创建。(也就是说,大小在创建时是固定的,但单个元素可以通过 lua_replace()
修改。)
弄清楚这些选项中的哪一个适合什么,可能是一个挑战,但我提供我自己的非常个人化的指南
使用 upvalue
使用闭包自己的环境表
使用线程的环境表
使用注册表
唯一线程安全的选项是堆栈和线程的环境表。如果lua_lock
和lua_unlock
被正确定义,你无法通过存储到注册表或闭包的上值数组或环境表来破坏 Lua 状态,但没有任何东西可以阻止另一个(操作系统)线程同时存储其他东西,从而导致标准的竞争条件,其中访问和更新被另一个进程中断。特别是,这适用于 lauxlib 的引用实现(如果存储在注册表中)。然而,有了所有可用的环境表,现在应该更容易避免这种情况。
线程的环境表是线程安全的,前提是每个 Lua 线程都映射到单个操作系统线程(它可以是多对一映射);也就是说,给定的 Lua 线程始终在同一个操作系统线程中运行。这使得它成为保存 lauxlib 风格引用的一个有吸引力的选项,如果无法完全避免使用它们。
闭包的上值数组和环境表在相同条件下是线程安全的,但这通常更难保证。对于短生命周期闭包(例如,返回给实现迭代器的闭包)来说,这很可能是这种情况,但对于长生命周期闭包(例如,库函数)来说,这不太可能。
如果你发现自己在多线程环境中修改注册表,你应该认真考虑用某种形式的锁来保护修改。这也适用于使用基于注册表的lauxlib
功能,例如luaL_newmetatable()
,如果它的使用不限于线程初始化之前的阶段。