- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
最近熟悉 go 项目时,发现项目中有用到 github.com/yuin/gopher-lua 这个包,之前并没有接触过,特意去看了官方文档和找了些网上的资料,特此记录下.
本次介绍计划分为两篇文章,这一次主要介绍 github.com/yuin/gopher-lua 这个包的介绍以及基础使用,下一边将介绍 github.com/yuin/gopher-lua 是如何在项目中使用的。如有不对的地方,请不吝赐教,谢谢.
文章中的 gopher-lua 如果没有特别说明,即为:github.com/yuin/gopher-lua.
我们先开看看官方是如何介绍自己的:
GopherLua is a Lua5.1(+ goto statement in Lua5.2) VM and compiler written in Go. GopherLua has a same goal with Lua: Be a scripting language with extensible semantics . It provides Go APIs that allow you to easily embed a scripting language to your Go host programs.
GopherLua是一个Lua5.1(Lua5.2中的+goto语句)虚拟机和用Go编写的编译器。GopherLua与Lua有着相同的目标:成为一种具有可扩展语义的脚本语言。它提供了Go API,允许您轻松地将脚本语言嵌入到Go主机程序中.
看上面的翻译还是有点抽象,说说自己的理解。 github.com/yuin/gopher-lua 是一个纯 Golang 实现的 Lua 虚拟机,它能够很轻松的在 go 写的程序中调用 lua 脚本。另外提一嘴, 使用插件后,也能够在 lua 脚本中调用 go 写好的代码 。挺秀的! 。
接下来我们看一看, github.com/yuin/gopher-lua 的性能如何,这里就直接引用官方自己做的测试来介绍。详情见 wiki page 链接。点进链接过后,发现性能还不错,执行效率和性能仅比 C 实现的 bindings 差点.
官方测试例子是生成 斐波那契数列 ,测试执行结果如下:
prog | time |
---|---|
anko | 182.73s |
otto | 173.32s |
go-lua | 8.13s |
Python3.4 | 5.84s |
GopherLua | 5.40s |
lua5.1.4 | 1.71s |
下面的介绍,都是基于 v1.1.0 版本进行的.
go get github.com/yuin/gopher-lua@v1.1.0
Go 的版本需要 >= 1.9 。
这里写一个简单的程序,了解 gopher-lua 是如何使用的.
package main
import (
lua "github.com/yuin/gopher-lua"
)
func main() {
// 1、创建 lua 的虚拟机
L := lua.NewState()
// 执行完毕后关闭虚拟机
defer L.Close()
// 2、加载fib.lua
if err := L.DoString(`print("hello world")`); err != nil {
panic(err)
}
}
执行结果:
hello world
看到这里,感觉没啥特别的地方,接下来,我们看一看 gopher-lua 如何调用事先写好的 lua脚本 .
fib.lua 脚本内容:
function fib(n)
if n < 2 then return n end
return fib(n-1) + fib(n-2)
end
main.go 。
package main
import (
"fmt"
lua "github.com/yuin/gopher-lua"
)
func main() {
// 1、创建 lua 的虚拟机
L := lua.NewState()
defer L.Close()
// 加载fib.lua
if err := L.DoFile(`fib.lua`); err != nil {
panic(err)
}
// 调用fib(n)
err := L.CallByParam(lua.P{
Fn: L.GetGlobal("fib"), // 获取fib函数引用
NRet: 1, // 指定返回值数量
Protect: true, // 如果出现异常,是panic还是返回err
}, lua.LNumber(10)) // 传递输入参数n
if err != nil {
panic(err)
}
// 获取返回结果
ret := L.Get(-1)
// 从堆栈中扔掉返回结果
// 这里一定要注意,不调用此方法,后续再调用 L.Get(-1) 获取的还是上一次执行的结果
// 这里大家可以自己测试下
L.Pop(1)
// 打印结果
res, ok := ret.(lua.LNumber)
if ok {
fmt.Println(int(res))
} else {
fmt.Println("unexpected result")
}
}
执行结果:
55 。
从上面我们已经能够感受到部分 gopher-lua 的魅力了。接下来,我们就一起详细的学习学习 gopher-lua .
All data in a GopherLua program is an LValue . LValue is an interface type that has following methods. 。
GopherLua程序中的所有数据都是一个LValue。LValue是一种具有以下方法的接口类型.
String() string
Type() LValueType
// value.go:29
type LValue interface {
String() string
Type() LValueType
// to reduce `runtime.assertI2T2` costs, this method should be used instead of the type assertion in heavy paths(typically inside the VM).
assertFloat64() (float64, bool)
// to reduce `runtime.assertI2T2` costs, this method should be used instead of the type assertion in heavy paths(typically inside the VM).
assertString() (string, bool)
// to reduce `runtime.assertI2T2` costs, this method should be used instead of the type assertion in heavy paths(typically inside the VM).
assertFunction() (*LFunction, bool)
}
上面来自官方的介绍,接下来我们看看 gopher-lua 支持那些数据类型.
Type name | Go type | Type() value | Constants |
---|---|---|---|
LNilType |
(constants) | LTNil |
LNil |
LBool |
(constants) | LTBool |
LTrue , LFalse |
LNumber |
float64 | LTNumber |
- |
LString |
string | LTString |
- |
LFunction |
struct pointer | LTFunction |
- |
LUserData |
struct pointer | LTUserData |
- |
LState |
struct pointer | LTThread |
- |
LTable |
struct pointer | LTTable |
- |
LChannel |
chan LValue | LTChannel |
- |
具体的实现,大家有兴趣,可以自己去看看源码,这里就不做分析了.
那我们是如何知道 go 调用 lua 函数后,得到结果的类型呢?我们可以通过以下方式来知道:
package main
import (
"fmt"
lua "github.com/yuin/gopher-lua"
)
func main() {
// 1、创建 lua 的虚拟机
L := lua.NewState()
defer L.Close()
// 加载fib.lua
if err := L.DoFile(`fib.lua`); err != nil {
panic(err)
}
TestString(L)
}
func TestString(L *lua.LState) {
err := L.CallByParam(lua.P{
Fn: L.GetGlobal("TestLString"), // 获取函数引用
NRet: 1, // 指定返回值数量
Protect: true, // 如果出现异常,是panic还是返回err
})
if err != nil {
panic(err)
}
lv := L.Get(-1) // get the value at the top of the stack
// 从堆栈中扔掉返回结果
L.Pop(1)
if str, ok := lv.(lua.LString); ok {
// lv is LString
fmt.Println(string(str))
}
if lv.Type() != lua.LTString {
panic("string required.")
}
}
fib.lua中的代码:
function TestLString()
return "this is test"
end
接下来看看指针类型是如何判断的:
lv := L.Get(-1) // get the value at the top of the stack
if tbl, ok := lv.(*lua.LTable); ok {
// lv is LTable
fmt.Println(L.ObjLen(tbl))
}
特别注意:
LBool
, LNumber
, LString
这三类不是指针类型,其他的都属于指针类型。 LNilType and LBool
这里没看懂官方在说什么,知道的可以告知下,谢谢。 nil和false
都是认为是错误的情况。 nil
表示一个无效值(在条件表达式中相当于false)。 大家有不明白的地方,推荐去看看官方怎么说的.
官方还介绍了性能优化这块的内容,我就不介绍了,大家感兴趣可以去看官方.
主要是对于我这种非科班出生的菜鸟来说,还是有点难度的,这里就不瞎说了,免得误导大家。哈哈...... 。
一般来说,使用默认的方式,性能不会太差。对性能没有特别高的要求,也没有必要去折腾这个.
test.lua 脚本内容:
print(double(100))
main.go 中的内容:
package main
import (
"fmt"
lua "github.com/yuin/gopher-lua"
)
func main() {
L := lua.NewState()
defer L.Close()
L.SetGlobal("double", L.NewFunction(Double)) /* Original lua_setglobal uses stack... */
L.DoFile("test.lua")
}
func Double(L *lua.LState) int {
fmt.Println("coming go code.............")
lv := L.ToInt(1) /* get argument */
L.Push(lua.LNumber(lv * 2)) /* push result */
return 1 /* number of results */
}
执行结果:
coming go code.............
200
上面我们已经实现了一个简单的 lua 脚本中调用 go 代码的功能.
上面介绍了 lua 中调用 Go中的代码,Go提供的功能不多还好,直接使用即可,但是实际项目中,既然使用到了Go和lua结合的模式,必然会存在Go提供基础功能,lua来编写业务的方式,这个时候如果还是使用上面的方式,使用起来将非常不方便。这里提供了一种方式,将Go中的功能封装成一个模块,提供给 lua 使用,这样就方便许多.
接下来我们一起看看怎么做.
mymodule.go 的内容:
package main
import (
"fmt"
lua "github.com/yuin/gopher-lua"
)
func Loader(L *lua.LState) int {
// register functions to the table
mod := L.SetFuncs(L.NewTable(), exports)
// register other stuff
L.SetField(mod, "name", lua.LString("testName"))
// returns the module
L.Push(mod)
return 1
}
var exports = map[string]lua.LGFunction{
"MyAdd": MyAdd,
}
func MyAdd(L *lua.LState) int {
fmt.Println("coming custom MyAdd")
x, y := L.ToInt(1), L.ToInt(2)
// 原谅我还不知道怎么把计算结果返回给 lua ,太菜了啦
// 不过用上另外一个包后,我知道,具体看实战篇。
fmt.Println(x)
fmt.Println(y)
return 1
}
main.go 的内容:
package main
import lua "github.com/yuin/gopher-lua"
func main() {
L := lua.NewState()
defer L.Close()
L.PreloadModule("myModule", Loader)
if err := L.DoFile("main.lua"); err != nil {
panic(err)
}
}
main.lua 的内容
local m = require("myModule")
m.MyAdd(10, 20)
print(m.name)
运行 main.go 得到执行结果:
coming custom MyAdd
10
20
testName
lua 中的代码 。
function TestGoCallLua(x, y)
return x+y, x*y
end
go 中的代码 。
func TestTestGoCallLua(L *lua.LState) {
err := L.CallByParam(lua.P{
Fn: L.GetGlobal("TestGoCallLua"), // 获取函数引用
NRet: 2, // 指定返回值数量,注意这里的值是 2
Protect: true, // 如果出现异常,是panic还是返回err
}, lua.LNumber(10), lua.LNumber(20))
if err != nil {
panic(err)
}
multiplicationRet := L.Get(-1)
addRet := L.Get(-2)
if str, ok := multiplicationRet.(lua.LNumber); ok {
fmt.Println("multiplicationRet is: ", int(str))
}
if str, ok := addRet.(lua.LNumber); ok {
fmt.Println("addRet is: ", int(str))
}
}
具体的可以看 xxx 中的 TestTestGoCallLua 函数.
执行结果:
multiplicationRet is: 200
addRet is: 30
这里我们直接使用官方的例子:
type Person struct {
Name string
}
const luaPersonTypeName = "person"
// Registers my person type to given L.
func registerPersonType(L *lua.LState) {
mt := L.NewTypeMetatable(luaPersonTypeName)
L.SetGlobal("person", mt)
// static attributes
L.SetField(mt, "new", L.NewFunction(newPerson))
// methods
L.SetField(mt, "__index", L.SetFuncs(L.NewTable(), personMethods))
}
// Constructor
func newPerson(L *lua.LState) int {
person := &Person{L.CheckString(1)}
ud := L.NewUserData()
ud.Value = person
L.SetMetatable(ud, L.GetTypeMetatable(luaPersonTypeName))
L.Push(ud)
return 1
}
// Checks whether the first lua argument is a *LUserData with *Person and returns this *Person.
func checkPerson(L *lua.LState) *Person {
ud := L.CheckUserData(1)
if v, ok := ud.Value.(*Person); ok {
return v
}
L.ArgError(1, "person expected")
return nil
}
var personMethods = map[string]lua.LGFunction{
"name": personGetSetName,
}
// Getter and setter for the Person#Name
func personGetSetName(L *lua.LState) int {
p := checkPerson(L)
if L.GetTop() == 2 {
p.Name = L.CheckString(2)
return 0
}
L.Push(lua.LString(p.Name))
return 1
}
func main() {
L := lua.NewState()
defer L.Close()
registerPersonType(L)
if err := L.DoString(`
p = person.new("Steeve")
print(p:name()) -- "Steeve"
p:name("Alice")
print(p:name()) -- "Alice"
`); err != nil {
panic(err)
}
}
官方还讲解了如何使用 go 中的context 来结束lua代码的执行,这里我就不演示了,大家自行研究.
这里直接放官方的文档,大家自行理解 。
The LState is not goroutine-safe. It is recommended to use one LState per goroutine and communicate between goroutines by using channels.
LState不是goroutine安全的。建议每个goroutine使用一个LState,并通过使用通道在goroutine之间进行通信.
Channels are represented by channel objects in GopherLua. And a channel table provides functions for performing channel operations.
在GopherLua中,通道由通道对象表示。通道表提供了执行通道操作的函数。这意味着,我们可以使用通道对象来创建、发送和接收消息,并使用通道表中的函数来控制通道的行为。通道是一种非常有用的并发编程工具,可以帮助我们在不同的goroutine之间进行通信和同步。通过使用GopherLua中的通道对象和通道表,我们可以轻松地在Lua代码中实现并发编程.
Some objects can not be sent over channels due to having non-goroutine-safe objects inside itself.
某些对象无法通过通道发送,因为其内部有非goroutine安全的对象.
上面这四种类型就不支持往通道中发送.
package main
import (
lua "github.com/yuin/gopher-lua"
"time"
)
func receiver(ch, quit chan lua.LValue) {
L := lua.NewState()
defer L.Close()
L.SetGlobal("ch", lua.LChannel(ch))
L.SetGlobal("quit", lua.LChannel(quit))
if err := L.DoString(`
local exit = false
while not exit do
-- 这个 channel 的写法是固定的 ??
channel.select(
{"|<-", ch, function(ok, v)
if not ok then
print("channel closed")
exit = true
else
print("received:", v)
end
end},
{"|<-", quit, function(ok, v)
print("quit")
exit = true
end}
)
end
`); err != nil {
panic(err)
}
}
func sender(ch, quit chan lua.LValue) {
L := lua.NewState()
defer L.Close()
L.SetGlobal("ch", lua.LChannel(ch))
L.SetGlobal("quit", lua.LChannel(quit))
if err := L.DoString(`
ch:send("1")
ch:send("2")
`); err != nil {
panic(err)
}
ch <- lua.LString("3")
quit <- lua.LTrue
}
func main() {
ch := make(chan lua.LValue)
quit := make(chan lua.LValue)
go receiver(ch, quit)
go sender(ch, quit)
time.Sleep(3 * time.Second)
}
执行结果:
received: 1
received: 2
received: 3
quit
下面这些内容,主要来自参考的文章,大家可以点击 当 Go 遇上了 Lua 查看原文.
如果侵权,请联系删除,谢谢.
在查看上述 DoString(...) 方法的调用链后,我们发现每执行一次 DoString(...) 或 DoFile(...) ,都会各执行一次 parse 和 compile .
func (ls *LState) DoString(source string) error {
if fn, err := ls.LoadString(source); err != nil {
return err
} else {
ls.Push(fn)
return ls.PCall(0, MultRet, nil)
}
}
func (ls *LState) LoadString(source string) (*LFunction, error) {
return ls.Load(strings.NewReader(source), "<string>")
}
func (ls *LState) Load(reader io.Reader, name string) (*LFunction, error) {
chunk, err := parse.Parse(reader, name)
// ...
proto, err := Compile(chunk, name)
// ...
}
从这一点考虑,在同份 Lua 代码将被执行多次(如 在 http server 中,每次请求将执行相同 Lua 代码 )的场景下,如果我们能够对代码进行提前编译,那么应该能够减少 parse 和 compile 的开销(如果这属于 hotpath 代码)。根据 Benchmark 结果,提前编译确实能够减少不必要的开销.
package glua_test
import (
"bufio"
"os"
"strings"
lua "github.com/yuin/gopher-lua"
"github.com/yuin/gopher-lua/parse"
)
// 编译 lua 代码字段
func CompileString(source string) (*lua.FunctionProto, error) {
reader := strings.NewReader(source)
chunk, err := parse.Parse(reader, source)
if err != nil {
return nil, err
}
proto, err := lua.Compile(chunk, source)
if err != nil {
return nil, err
}
return proto, nil
}
// 编译 lua 代码文件
func CompileFile(filePath string) (*lua.FunctionProto, error) {
file, err := os.Open(filePath)
defer file.Close()
if err != nil {
return nil, err
}
reader := bufio.NewReader(file)
chunk, err := parse.Parse(reader, filePath)
if err != nil {
return nil, err
}
proto, err := lua.Compile(chunk, filePath)
if err != nil {
return nil, err
}
return proto, nil
}
func BenchmarkRunWithoutPreCompiling(b *testing.B) {
l := lua.NewState()
for i := 0; i < b.N; i++ {
_ = l.DoString(`a = 1 + 1`)
}
l.Close()
}
func BenchmarkRunWithPreCompiling(b *testing.B) {
l := lua.NewState()
proto, _ := CompileString(`a = 1 + 1`)
lfunc := l.NewFunctionFromProto(proto)
for i := 0; i < b.N; i++ {
l.Push(lfunc)
_ = l.PCall(0, lua.MultRet, nil)
}
l.Close()
}
// goos: darwin
// goarch: amd64
// pkg: glua
// BenchmarkRunWithoutPreCompiling-8 100000 19392 ns/op 85626 B/op 67 allocs/op
// BenchmarkRunWithPreCompiling-8 1000000 1162 ns/op 2752 B/op 8 allocs/op
// PASS
// ok glua 3.328s
看到这里的需要注意,官方提醒我们,在每个 goroutine 。
在同份 Lua 代码被执行的场景下,除了可使用提前编译优化性能外,我们还可以引入虚拟机实例池.
因为新建一个 Lua 虚拟机会涉及到大量的内存分配操作,如果采用每次运行都重新创建和销毁的方式的话,将消耗大量的资源。引入虚拟机实例池,能够复用虚拟机,减少不必要的开销.
func BenchmarkRunWithoutPool(b *testing.B) {
for i := 0; i < b.N; i++ {
l := lua.NewState()
_ = l.DoString(`a = 1 + 1`)
l.Close()
}
}
func BenchmarkRunWithPool(b *testing.B) {
pool := newVMPool(nil, 100)
for i := 0; i < b.N; i++ {
l := pool.get()
_ = l.DoString(`a = 1 + 1`)
pool.put(l)
}
}
// goos: darwin
// goarch: amd64
// pkg: glua
// BenchmarkRunWithoutPool-8 10000 129557 ns/op 262599 B/op 826 allocs/op
// BenchmarkRunWithPool-8 100000 19320 ns/op 85626 B/op 67 allocs/op
// PASS
// ok glua 3.467s
Benchmark 结果显示,虚拟机实例池的确能够减少很多内存分配操作.
下面给出了 README 提供的实例池实现,但注意到该实现在初始状态时, 并未创建足够多的虚拟机实例 (初始时,实例数为 0),以及存在 slice 的动态扩容问题 ,这都是值得改进的地方.
type lStatePool struct {
m sync.Mutex
saved []*lua.LState
}
func (pl *lStatePool) Get() *lua.LState {
pl.m.Lock()
defer pl.m.Unlock()
n := len(pl.saved)
if n == 0 {
return pl.New()
}
x := pl.saved[n-1]
pl.saved = pl.saved[0 : n-1]
return x
}
func (pl *lStatePool) New() *lua.LState {
L := lua.NewState()
// setting the L up here.
// load scripts, set global variables, share channels, etc...
return L
}
func (pl *lStatePool) Put(L *lua.LState) {
pl.m.Lock()
defer pl.m.Unlock()
pl.saved = append(pl.saved, L)
}
func (pl *lStatePool) Shutdown() {
for _, L := range pl.saved {
L.Close()
}
}
// Global LState pool
var luaPool = &lStatePool{
saved: make([]*lua.LState, 0, 4),
}
参考链接:
github.com/yuin/gopher-lua 。
当 Go 遇上了 Lua 。
最后此篇关于浅谈如何使用github.com/yuin/gopher-lua的文章就讲到这里了,如果你想了解更多关于浅谈如何使用github.com/yuin/gopher-lua的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
服务架构进化论 原始分布式时代 一直以来,我可能和大多数的人认知一样,认为我们的服务架构的源头是单体架构,其实不然,早在单体系
序列化和反序列化相信大家都经常听到,也都会用, 然而有些人可能不知道:.net为什么要有这个东西以及.net frameword如何为我们实现这样的机制, 在这里我也是简单谈谈我对序列化和反序列化的
内容,是网站的核心所在。要打造一个受用户和搜索引擎关注的网站,就必须从网站本身的内容抓起。在时下这个网络信息高速发展的时代,许多低质量的信息也在不断地充斥着整个网络,而搜索引擎对一些高质量的内容
从第一台计算机问世到现在计算机硬件技术已经有了很大的发展。不管是现在个人使用的PC还是公司使用的服务器。双核,四核,八核的CPU已经非常常见。这样我们可以将我们程序分摊到多个计算机CPU中去计算,在
基本概念: 浅拷贝:指对象的字段被拷贝,而字段引用的对象不会被拷贝,拷贝对象和原对象仅仅是引用名称有所不同,但是它们共用一份实体。对任何一个对象的改变,都会影响到另外一个对象。大部分的引用类型,实
.NET将原来独立的API和SDK合并到一个框架中,这对于程序开发人员非常有利。它将CryptoAPI改编进.NET的System.Security.Cryptography名字空间,使密码服务摆脱
文件与文件流的区别(自己的话): 在软件开发过程中,我们常常把文件的 “读写操作” ,与 “创造、移动、复制、删除操作” 区分开来
1. 前言 单元测试一直都是"好处大家都知道很多,但是因为种种原因没有实施起来"的一个老大难问题。具体是否应该落地单元测试,以及落地的程度, 每个项目都有自己的情况。 本篇为
事件处理 1、事件源:任何一个HTML元素(节点),body、div、button 2、事件:你的操作 &
1、什么是反射? 反射 (Reflection) 是 Java 的特征之一,它允许运行中的 Java 程序获取自身的信息,并且可以操作类或对象的内部属性。 Oracle 官方对
1、源码展示 ? 1
Java 通过JDBC获得连接以后,得到一个Connection 对象,可以从这个对象获得有关数据库管理系统的各种信息,包括数据库中的各个表,表中的各个列,数据类型,触发器,存储过程等各方面的信息。
可能大家谈到反射面部肌肉都开始抽搐了吧!因为在托管语言里面,最臭名昭著的就是反射!它的性能实在是太低了,甚至在很多时候让我们无法忍受。不过不用那么纠结了,老陈今天就来分享一下如何来优化反射!&nbs
1. 前言 最近一段时间一直在研究windows 驱动开发,简单聊聊。 对比 linux,windows 驱动无论是市面上的书籍,视频还是社区,博文以及号主,写的人很少,导
问题:ifndef/define/endif”主要目的是防止头文件的重复包含和编译 ========================================================
不知不觉.Net Core已经推出到3.1了,大多数以.Net为技术栈的公司也开始逐步的切换到了Core,从业也快3年多了,一直坚持着.不管环境
以前面试的时候经常会碰到这样的问题.,叫你写一下ArrayList.LinkedList.Vector三者之间的区别与联系:原先一直搞不明白,不知道这三者之间到底有什么区别?哎,惭愧,基础太差啊,木
目录 @RequestParam(required = true)的误区 先说结论 参数总结 @RequestParam(r
目录 FTP、FTPS 与 SFTP 简介 FTP FTPS SFTP FTP 软件的主动模式和被动模式的区别
1、Visitor Pattern 访问者模式是一种行为模式,允许任意的分离的访问者能够在管理者控制下访问所管理的元素。访问者不能改变对象的定义(但这并不是强制性的,你可以约定为允许改变)。对管
我是一名优秀的程序员,十分优秀!