Environment Tables |
|
In Lua 5.0.2, Lua closures (but not C closures) have environment tables. The environment table is used to look up unbound names (that is, "global variables"). When a new closure is created, by executing a function expression or statement, the new closure acquires the environment table of the currently executing closure, so in the absence of any changes, all closures share the same environment table, which makes it pretty much a globals table as in previous versions of Lua.
The environment table of a closure can be accessed in C with lua_getfenv()
and in Lua with getfenv()
; it can be set to a different table with lua_setfenv()
/ setfenv()
. See the manual for these functions.
In 5.0.2, C closures effectively share a single globals table, which is associated with the currently executing thread. This table is available to the C closure by using the pseudo-index LUA_GLOBALSINDEX
(and indeed can be modified with lua_replace(L, LUA_GLOBALSINDEX)
). This is a useful but can be a bit messy.
In 5.1, every C closure has its own environment table reference. However, the LUA_GLOBALSINDEX
pseudo-index has not been altered, so that existing code which references LUA_GLOBALSINDEX
will still be referring to the "globals" table of the currently executing thread. (That table is now referred to as the thread's environment table, and can be accessed and modified with lua_getfenv and lua_setfenv applied to the thread object itself.) In order to access the closure's environment table, one uses LUA_ENVIRONINDEX
; a closure environment table (for either a C closure or a Lua closure) can be accessed and modified with lua_getfenv()
and lua_setfenv()
, applied to the closure object.
You can replace "your own" environment with lua_replace(L, LUA_ENVIRONINDEX)
, just as you can change the globals table with lua_replace(L, LUA_GLOBALSINDEX)
. However, the more common case is to call lua_setfenv()
on a newly-created closure.
(Full) userdata also have environment tables, although as I have argued elsewhere (UserDataRefinement), the name is confusing. Userdata environment tables are only a place to store information relevant to the userdata; no pseudo-index exists for them.
Newly created closures (and userdata) acquire their environment tables from the currently executing closure, if there is one, and otherwise from the current thread. (Again, as I have argued elsewhere, this is not a particularly useful default for userdata, but I won't go into that again.)
lua_pushcclosure()
. (That is, the size is fixed at creation, but individual elements can be modified by lua_replace()
.)
Figuring out which of these options to use for what can be a challenge, but I offer my own very personal guide:
Use upvalues:
Use the closure's own environment table:
Use the thread's environment table:
Use the registry:
The only options which are threadsafe are the stack and the thread's environment table. If lua_lock
and lua_unlock
are appropriately defined, you cannot corrupt the Lua state by storing into the registry or the closure's upvalue array or environment table, but nothing stops another (OS) thread from storing something else at the same time, leading to the standard race condition where access and update are interrupted by another process. In particular, this applies to lauxlib's implementation of references (if stored in the registry). With all the various environment tables available, however, this should now be much easier to avoid.
The thread's environment table is threadsafe provided that each Lua thread is mapped onto a single OS thread (it can be a many-to-one mapping); that is, a given Lua thread is always run in the same OS thread. That makes it an attractive option for keeping lauxlib-style references, if it is not possible to avoid using them altogether.
The closure's upvalue array and environment table are threadsafe under the same conditions, but this is often harder to guarantee. It will likely be the case for short-lifetime closures, such as ones returned to implemented an iterator, but it unlikely to be the case for long-lifetime closures, such as library functions.
If you do find yourself modifying the registry in a multi-threaded environment, you should think seriously about protecting the modification with a lock of some form. This also applies to the use of registry-based lauxlib
features such as luaL_newmetatable()
, if its use is not restricted to the pre-thread initialization stage.