在玩GitHub的时候,偶然发现了gopher-lua,一个用纯Golang实现的Lua虚拟机。我们知道Golang是静态语言,而Lua是动态语言。Golang的性能和效率在语言中是非常优秀的,但是在动态能力上绝对比不上Lua。那么如果能把两者结合起来,就可以结合各自的长处了(手动滑稽。在项目wiki中可以知道gopher-lua的执行效率和性能只差于C实现的bindings。因此,从性能方面的考虑,这应该是一个很好的虚拟机解决方案HelloWorld下面是一个简单的HelloWorld程序,我们先新建一个虚拟机,然后用DoString(...)解释执行lua的操作代码,***关闭虚拟机,执行程序,我们会在命令行看到“HelloWorld”的字符串。l:=lua.NewState()deferl.Close()iferr:=l.DoString(`print("HelloWorld")`);err!=nil{panic(err)}}//HelloWorld查看后提前编译上面的DoString(...)方法的调用链之后,我们发现每次执行DoString(...)或DoFile(...)时,parse和compile都会ex分别执行。func(ls*LState)DoString(sourcestring)error{iffn,err:=ls.LoadString(source);err!=nil{returnerr}else{ls.Push(fn)returnls.PCall(0,MultRet,nil)}}func(ls*LState)LoadString(sourcestring)(*LFunction,error){returnls.Load(strings.NewReader(source),"")}func(ls*LState)Load(readerio.Reader,namestring)(*LFunction,error){chunk,err:=parse.Parse(reader,name)//...proto,err:=Compile(chunk,name)//...}从这个角度来看,在同样在Lua代码会被执行多次的场景下(比如在http服务器中,每次请求都会执行相同的Lua代码),如果我们能够提前编译代码,那么应该可以减少开销解析和编译(如果这属于热路径代码)。根据Benchmark的结果,提前编译确实可以减少不必要的开销。packageglua_testimport("bufio""os""strings"lua"github.com/yuin/gopher-lua""github.com/yuin/gopher-lua/parse")//指定lua的自定义函数functionCompileString(sourcestring)(*lua.FunctionProto,error){reader:=strings.NewReader(source)chunk,err:=parse.Parse(reader,source)iferr!=nil{returnnil,err}proto,err:=lua.Compile(chunk,source);iferr!=nil{returnil,err}returnproto,nil}//指定lua的默认函数CompileFile(filePathstring)(*lua.FunctionProto,error){file,err:=os.Open(filePath)deferfile.Close()iferr!=nil{returnil,err}reader:=bufio.NewReader(file)chunk,err:=parse.Parse(reader,filePath)iferr!=nil{returnnil,err}proto,err:=lua.Compile(chunk,filePath)).)iferr!=nil{returnil,err}returnproto,nil}funcBenchmarkRunWithoutPreCompiling(b*testing.B){l:=lua.NewState()fori:=0;i>26)}有了这个工具函数,我们就可以查看全局变量了。packagemain//...funcCheckGlobal(proto*lua.FunctionProto)error{for_,code:=rangeproto.Code{switchopGetOpCode(code){caselua.OP_GETGLOBAL:returnerrors.New("notallowtoaccessglobal")caselua.OP_SETGLOBAL:returnerrors.New("notallowtosetglobal")}}//检查嵌套函数的全局变量for_,nestedProto:=rangeproto.FunctionPrototypes{iferr:=CheckGlobal(nestedProto);err!=nil{returnerr}}returnnil}funcTestCheckGetGlobal(t*testing.T){l:=lua.NewState()proto,_:=CompileString(`print(_G)`)iferr:=CheckGlobal(proto);err==nil{t.Fail()}l.Close()}funcTestCheckSetGlobal(t*testing.T){l:=lua.NewState()proto,_:=CompileString(`_G={}`)iferr:=CheckGlobal(proto);err==nil{t.Fail()}l.Close()}module除了变量可能被污染外,导入的Gomodules也有可能在运行过程中被篡改。因此,我们需要一种机制来保证导入到虚拟机中的模块不被篡改,即导入的对象是只读的。查阅相关博客后,我们可以修改Table的__newindex方法,将模块设置为只读模式。packagemainimport("fmt""github.com/yuin/gopher-lua")//设置表为只读funcSetReadOnly(l*lua.LState,table*lua.LTable)*lua.LUserData{ud:=l.NewUserData()mt:=l.NewTable()//设置表中的字段为tablel.SetField(mt,"__index",table)//限制对表的更新操作l.SetField(mt,"__newindex",l.NewFunction(func(state*lua.LState)int{state.RaiseError("notallowtomodifytable")return0}))ud.Metatable=mtreturnud}funcload(l*lua.LState)int{mod:=l.SetFuncs(l.NewTable(),exports)l.SetField(mod,"name",lua.LString("gomodule"))//设置只读l.Push(SetReadOnly(l,mod))return1}varexports=map[字符串]lua。LGFunction{"goFunc":goFunc,}funcgoFunc(l*lua.LState)int{fmt.Println("golang")return0}funcmain(){l:=lua.NewState()l.PreloadModule("gomodule",load)//尝试修改引入的模块iferr:=l.DoString(`localm=require("gomodule");m.name="helloworld"`);err!=nil{fmt.Println(err)}l.close()}//:1:notallowtomodifytable写在***Golang和lua的融合开阔了我的眼界:原来静态语言和动态语言可以这样融合,静态语言的运行效率高,动态语言的开发效率高。发现没有关于Go-Lua的技术分享,只找到了一篇稍微相关的文章(京东三级列表页面持续架构优化-Golang+Lua(OpenResty)***实践),而这篇文章中Lua依然运行onC.由于资料匮乏和本人(学生党)开发经验不足,无法评估该方案在实际生产中的可行性。所以,本文只能作为“散文”,呵呵。