lua 环境揭秘

时间:2022-03-17 21:45:08

什么是环境?

http://www.lua.org/manual/5.1/manual.html#2.9

Besides metatables, objects of types thread, function, and userdata have another table associated with them, called their environment. Like metatables, environments are regular tables and multiple objects can share the same environment.

线程、函数和用户自定义数据, 一张表与之关联,即环境表, 环境表也是章普通表, 多个对象可以共享同一个环境表。

Threads are created sharing the environment of the creating thread. Userdata and C functions are created sharing the environment of the creating C function. Non-nested Lua functions (created by loadfile, loadstring or load) are created sharing the environment of the creating thread. Nested Lua functions are created sharing the environment of the creating Lua function.

被创建的线程共享创建线程的环境。

被创建的用户数据和C函数共享 创建者(C函数)的环境。

Non-nested Lua 函数(由loadfile和loastring和load)被创建后, 共享创建者(线程)的环境。

Nested Lua函数被创建,共享创建者(Lua函数)的环境。

Environments associated with threads are called global environments. They are used as the default environment for threads and non-nested Lua functions created by the thread and can be directly accessed by C code (see §3.3).

关联线程的环境, 被称作全局环境。 线程和Non-nested函数被线程创建后, 默认的环境就是全局环境, 这些环境可以被C代码直接访问。

The environment associated with a C function can be directly accessed by C code (see §3.3). It is used as the default environment for other C functions and userdata created by the function.

C函数关联的环境,可以直接被C代码访问。

C函数创建的C函数和用户数据, 则将这个环境被作为默认环境。

Environments associated with Lua functions are used to resolve all accesses to global variables within the function (see §2.3). They are used as the default environment for nested Lua functions created by the function.

lua函数关联的环境,被用作解析所有的全局函数的访问, 在此函数中。

这个环境被用作如下情况的默认环境, lua函数创建的了nested lua函数, 则nested lua函数,则集成了 lua函数的环境。

You can change the environment of a Lua function or the running thread by calling setfenv. You can get the environment of a Lua function or the running thread by calling getfenv. To manipulate the environment of other objects (userdata, C functions, other threads) you must use the C API.

使用setfenv改变lua函数或者线程的环境。

使用getfenv获取lua函数或者线程的环境。

操作其它对象(userdata c函数 其它线程)的环境, 需要使用C API.

LUA thread

The type thread represents independent threads of execution and it is used to implement coroutines (see §2.11). Do not confuse Lua threads with operating-system threads. Lua supports coroutines on all systems, even those that do not support threads.

线程表达了一个独立的执行线索,它用于实现协程。

不要把lua的线程的操作系统的线程混淆。

lua支持协程在所有的系统上, 即使在那些不支持线程的系统上。

协程

Lua supports coroutines, also called collaborative multithreading. A coroutine in Lua represents an independent thread of execution. Unlike threads in multithread systems, however, a coroutine only suspends its execution by explicitly calling a yield function.

http://www.lua.org/manual/5.1/manual.html#2.11

http://www.cnblogs.com/chenny7/p/3634529.html

协同程序(coroutine)与多线程情况下的线程比较类似:有自己的堆栈、局部变量、指令指针,但与其它协程共享全局变量等很多信息。

协程类似一种多线程,但与多线程还有很多区别:

1. 协程并非os线程,所以创建、切换开销比线程相对要小。
2. 协程与线程一样有自己的栈、局部变量等,但是协程的栈是在用户进程空间模拟的,所以创建、切换开销很小。
3. 多线程程序是多个线程并发执行,也就是说在一瞬间有多个控制流在执行。而协程强调的是一种多个协程间协作的关系,只有当一个协程主动放弃执行权,另一个协程才能获得执行权,所以在某一瞬间,多个协程间只有一个在运行。
4. 由于多个协程时只有一个在运行,所以对于临界区的访问不需要加锁,而多线程的情况则必须加锁。
5. 多线程程序由于有多个控制流,所以程序的行为不可控,而多个协程的执行是由开发者定义的所以是可控的。

Lua的协程是不对称的(asymmetric coroutines),是指“挂起一个正在执行的协同函数” 和 “使一个被挂起的协程再次执行的函数”是不同的。

有些语言使用对称协同(symmetric coroutines),即使用同一个函数负责“执行与挂起间的状态切换”。

Non-nested lua function

Non-nested Lua functions (created by loadfile, loadstring or load) are created sharing the environment of the creating thread.

Non-nested lua functions 没有具体例子说明,到底是什么样的函数, 只说是由 loadfile  loadstring 和 load产生的, 可以理解为脚本文件对应的一个匿名函数。

这些函数继承了 创建者线程的 环境。

Environments associated with threads are called global environments. They are used as the default environment for threads and non-nested Lua functions created by the thread and can be directly accessed by C code (see §3.3).

线程相关的环境被称为全局环境, 当线程创建了  线程或者 non-nested lua函数 时候,  被创建者的默认环境, 就继承了创建者线程的环境。

所以, Non-nested lua functions 也具有全局环境。

对于 require语句, 调用一个脚本情况, require实际上相当于 local  f=loadfile(filename); f()

整个脚本对应的匿名函数, 就具有创建者线程相同的环境, 创建者线程通过 require方法加载, 查询loader,找到lua脚本的加载器:

static int loader_Lua (lua_State *L) {
  const char *filename;
  const char *name = luaL_checkstring(L, 1);
  filename = findfile(L, name, "path");
  if (filename == NULL) return 1;  /* library not found in this path */
  if (luaL_loadfile(L, filename) != 0)
    loaderror(L, filename);
  return 1;  /* library loaded successfully */
}

require函数的环境为 _G

require函数的实现简写为,  则filename脚本被创造为 chunk对应的 匿名函数 f, f默认继承了 require的环境 _G

function require(filename)

if package.loaded[filename] then

return package.loaded[filename]

end

local f = assert(loadfile(filename))

return f()

end

当然如果 filename 脚本中有 module语句, 则会改写 匿名函数的环境。

可以通过本文的给出的脚本进行验证。

main.lua

local module_mediator = require("module_mediator")

var_caller = 55

local function printTable(tbl)

for k,v in pairs(tbl) do
        print("kay="..tostring(k) .. "  value="..tostring(v))
    end

end

print("----------- module_mediator ------------------")

printTable(module_mediator)

print("----------- _G ------------------")

printTable(_G)

module_mediator.lua

local require = require
local setmetatable = setmetatable
local setfenv = setfenv
local getfenv = getfenv
local tostring = tostring
local print = print

module("module_mediator", package.seeall)

var_mediator = 77

--[[

local require_env = {var_mediator=33}
setmetatable(require_env, {__index=_G})

setfenv(require, require_env)
]]

local require_env = getfenv(require)

local function printTable(tbl)

for k,v in pairs(tbl) do
        print("kay="..tostring(k) .. "  value="..tostring(v))
    end

end

print(" require_env ="..tostring(require_env))

print("----------- before require env ------------------")

printTable(require_env)

print("----------- after require env ------------------")

local module_test = require("module_test")

module_test.lua (将module 添加和去除, 本脚本中的 getfenv(1) 和 getfenv(2)结果进行比较):

去掉module, 打印:

getfenv(1) =table: 005D2658  // module_test.lua匿名函数 环境
getfenv(1) metatable=nil
getfenv(2) =table: 005D2658 // require 的环境, _G

kay=_G  value=table: 005D2658

添加module, 打印:

getfenv(1) =table: 00773C68 // module_test.lua匿名函数 环境

getfenv(1) metatable=nil
getfenv(2) =table: 00252658 // require 的环境, _G

kay=_G  value=table: 00252658

--module(..., package.seeall)

local print = print
local getfenv = getfenv
local tostring = tostring
local getmetatable = getmetatable
local pairs = pairs

module("module_test")

local function printTable(tbl)

for k,v in pairs(tbl) do
        print("kay="..tostring(k) .. "  value="..tostring(v))
    end

end

varone = 1

local strkey = "vartwo"

-- 本模块环境
local env = getfenv(1)

env[strkey] = 2

print("vartwo="..vartwo)

print("getfenv(1) ="..tostring( ( getfenv(1) ) ) )

print("getfenv(1) metatable="..tostring( getmetatable( getfenv(1) ) ) )

if getmetatable( getfenv(1) )  then
print("--------------- before getmetatable( getfenv(1) ) -------------")
printTable( getmetatable( getfenv(1) ) )
print("--------------- after getmetatable( getfenv(1) ) -------------")
end

-- 调用此模块的环境
local env_caller = getfenv(2)

env["env_caller"] = env_caller

print("getfenv(2) ="..tostring(getfenv(2)))

print("--------------- before env_caller -------------")
printTable( env_caller )
print("--------------- after env_caller -------------")

脚本文件与chunk与匿名函数

http://www.lua.org/manual/5.1/manual.html#2.11

The unit of execution of Lua is called a chunk. A chunk is simply a sequence of statements, which are executed sequentially. Each statement can be optionally followed by a semicolon:

	chunk ::= {stat [`;´]}

There are no empty statements and thus ';;' is not legal.

lua的执行单元是chunk, chunk是有一系列的语句组成。

Lua handles a chunk as the body of an anonymous function with a variable number of arguments (see §2.5.9). As such, chunks can define local variables, receive arguments, and return values.

lua将一个chunk作为一个匿名函数对待, 带有一系列的入参。

所以,chunk可以定义局部变量, 接受参数, 返回值。

A chunk can be stored in a file or in a string inside the host program. To execute a chunk, Lua first pre-compiles the chunk into instructions for a virtual machine, and then it executes the compiled code with an interpreter for the virtual machine.

chunk存在两种形式, 文件 或者 字符串。

执行chunk需要先将chunk编译为指令,这个是虚拟机指令, 然后执行编译后的代码, 使用虚拟机解释器。

Chunks can also be pre-compiled into binary form; see program luac for details. Programs in source and compiled forms are interchangeable; Lua automatically detects the file type and acts accordingly.

chunk可以被预先编译为二进制形式, 详情参与 luac。

源码和编译形式是可以互换的。lua会自动探测, 文件类型, 并执行响应动作。

如果是编译后的文件, 则lua不会再编译,直接执行, 如果未编译, lua会先编译后执行。

lua require dofile loadfile区别

http://blog.163.com/hbu_lijian/blog/static/126129153201422902256778/

1.dofile与loadfile

dofile当作Lua运行代码的chunk的一种原始的操作。dofile实际上是一个辅助的函数。真正完成功能的函数是loadfile;与dofile不同的是loadfile编译代码成中间码并且返回编译后的chunk作为一个函数,而不执行代码;另外loadfile不会抛出错误信息而是返回错误代。我们可以这样定义dofile:

function dofile (filename)

local f = assert(loadfile(filename))

return f()

end

3.require与dofile

。粗略的说require和dofile完成同样的功能但有两点不同:

1. require会搜索目录加载文件

2. require会判断是否文件已经加载避免重复加载同一文件。由于上述特征,require在Lua中是加载库的更好的函数。

Nested lua luafunction

Nested Lua functions are created sharing the environment of the creating Lua function.

嵌套lua函数共享了  创建者的环境。

什么是嵌套的lua函数呢? 没有官方定义。

http://lua-users.org/wiki/LuaScopingDiscussion

这个有个关于嵌套函数作用域的讨论,  函数curry化的用法, 被嵌套的是 sum函数:

function addn(x)
function sum(y)
return x+y
end
return sum
end
print((addn(3))(1))

通过样例解释嵌套函数环境

getfenvtest.lua

local getfenv = getfenv
local print = print

module("getfenvtest")

var_test = 1

function test()

local env = getfenv(1)
    print("var_test="..env["var_test"])

local env_caller = getfenv(2)
    print("env_caller[var_test]="..env_caller["var_test"])
end

test()

getfenv_main.lua

var_test = 100

local testmodule = require("getfenvtest")

local function printTable(tbl)

for k,v in pairs(tbl) do
        print("kay="..tostring(k) .. "  value="..tostring(v))
    end

end

print("-------- before test func env ------")
printTable(getfenv(testmodule.test))
print("-------- after test func env ------")

function mediator()

testmodule.test()

end

local env = {}
env.testmodule = testmodule
env.mediatorflag = true
env.var_test = 55

setfenv(mediator, env)

mediator()

打印:

var_test=1  // getfenv_test.lua中 调用test打印,  说明test函数继承了 此脚本的匿名函数环境
env_caller[var_test]=1   // getfenv_test.lua中 调用test打印,  此脚本的匿名函数调用了 此函数, 故此函数中getfenv(2) 返回的是 此脚本匿名函数的环境

-------- before test func env ------ // 打印标明 虽然在 被调用脚本中使用, 但是test函数的环境,还是其创建时候的环境, 即getenvtest.lua中 module环境。
kay=_NAME  value=getfenvtest
kay=_PACKAGE  value=
kay=test  value=function: 00539898
kay=_M  value=table: 00539168
kay=var_test  value=1
-------- after test func env ------
var_test=1 // 虽然在被调用脚本中使用, getfenv获得的环境还是 getfenvtest.lua中module的环境, 跟调用点没有关系。
env_caller[var_test]=55 // 在被调用脚本中使用, getfenv(2)获得的是调用脚本的环境。
>Exit code: 0

一个有趣的环境改变实验

话说一个聪明人, 能够根据环境讲不同的话语, 见到美国人, 说美国是个伟大的国家, 见到中国人, 中国是个伟大的国家, 背地里没有外国人的时候, 说MyCountry是个伟大的国家。

首先定义 聪明人为 wiseman.lua 使用module构造。

其中有一个关键函数是  makeEnvDependFunc, 将一个普通的函数,转换为, 判断会判断环境的函数。

实现原理为 改造此函数的依赖的 环境 setfenv, 当判断被调用的场景getfenv(2), 如果存在, 则重写环境为 {}

并将此{}的metatable设置为 __index=function()end, 函数体中会先寻找 getfenv(2), 然后在找getfenv(1)。

module("wiseman", package.seeall)

country = "My country"

local function makeEnvDependFunc(f)
    local oldfenv = getfenv(f)

-- 缂撳瓨鎵€鏈夌殑env
    local envTbl = {}

table.insert( envTbl, oldfenv )

-- environment, and isprior
    local addNewEnv = function( env, isprior )
        if isprior then
            table.insert(envTbl, 1, env)
        else
            table.insert(envTbl, env)
        end
    end

local deleteNewEnv = function(targetEnv)
        for i,env in ipairs(envTbl) do
            if targetEnv == env then
                table.remove(envTbl, i)
            end
        end
    end

local indexEnvTbl = function(env, key)
        for i,env in ipairs(envTbl) do
            local val = env[key]
            if val then
                return val
            end
        end

return nil
    end

local envMetaTable = {__index=indexEnvTbl}
    local newfenv = {}
    setmetatable(newfenv, envMetaTable)

local fwrapper = function()
        addNewEnv(getfenv(2), true)

setfenv(f, newfenv)

local ret = f()

deleteNewEnv(getfenv(2))

setfenv(f, oldfenv)

return ret
    end

return fwrapper
end

--[[
function SayHello()
    local env = getfenv(1)

local country_senario = country

local env_caller = getfenv(2)
    if env_caller and env_caller.country then
        country_senario = env_caller.country
    end

print(country_senario .." is a great country!")
end
]]

-- 姝ゆ椂 country 杩樻槸鎸囨湰module锛?My Country
function SayHello_Naive()
    print(country .." is a great country!")
end

SayHello = makeEnvDependFunc(SayHello_Naive)

china.lua

module("china")

country = "china"

function CallPerson( wiseman )
    wiseman.SayHello()
end

american.lua

module("american")

country = "american"

function CallPerson( wiseman )
    wiseman.SayHello()
end

主调脚本actionbycountry.lua

local wiseman = require("wiseman")

local chinese = require("china")
chinese.CallPerson(wiseman)

local american = require("american")
american.CallPerson(wiseman)

wiseman.SayHello()

打印:

>lua -e "io.stdout:setvbuf 'no'" "actionbycountry.lua"
china is a great country!
american is a great country!
My country is a great country!
>Exit code: 0

实验脚本如下:

https://github.com/fanqingsong/code-snippet/tree/master/lua/envtest