Go的內(nèi)嵌腳本語言有很多牲平,Python語言就是一例。Python有豐富的用戶群體域滥,強大的第三方庫纵柿,廣泛的開源工具支持,Go的最佳伴侶應(yīng)該是Python启绰,可是Python的一些不足之處卻讓Go感到為難昂儒。最好用的開源的go-python庫是全局單例的Python解釋器,對于并發(fā)能力比較出色的Go語言來說委可,萬惡的GIL會讓Go運行時降級為單線程渊跋,很容易就成了運行的瓶頸。
看來Python這條路是走不下去了着倾,幸好拾酝,還有Lua。
Lua作為專業(yè)的內(nèi)置腳本語言卡者,它是單線程的運行的蒿囤,沒有操作系統(tǒng)級別的多線程,同一個進程可以運行多個Lua解釋器實例崇决,數(shù)據(jù)完全獨立材诽,互不干擾镶摘。它的學(xué)習(xí)成本比Python還要低廉,普通用戶大約花個30分鐘就可以把Lua語言的基本特性都學(xué)完了岳守。
Lua目前最好的golang開源項目是日本人實現(xiàn)的凄敢,叫GopherLua。
接下來我們逐步研究一下GopherLua如何使用湿痢,首先寫一個HelloWorld
輸出結(jié)果
注意我們使用NewState得到一個獨立的Lua解釋器實例涝缝,后續(xù)的所有操作都是基于這個實例內(nèi)部進行的,全局狀態(tài)限于L對象內(nèi)部譬重,沒有進程級別的全局狀態(tài)拒逮。如果要得到多個解釋器實例,使用NewState多創(chuàng)建幾個就行臀规。
也許你會想到golang有如此多的goroutine滩援,難道要每個goroutine都開一個lua解釋器實例么,如果這樣塔嬉,內(nèi)存肯定是要被撐爆的玩徊。
GopherLua考慮到了這點,它使用解釋器實例池解決了這個問題谨究。當(dāng)用戶想要使用Lua解釋器時恩袱,從池中取出一個,用完了再還回去胶哲。因為同一個解釋器可能要被多個協(xié)程使用畔塔,雖然不是同一時間被多個協(xié)程使用,要注意全局狀態(tài)不要相互干擾鸯屿。
下面我們使用GopherLua調(diào)用一個lua模塊
斐波那契數(shù)列使用獨立的lua腳本實現(xiàn)澈吨,golang使用DoFile加載腳本,然后使用CallByParam調(diào)用腳本中的fib全局函數(shù)寄摆,最后獲取返回結(jié)果打印輸出谅辣。
GopherLua的函數(shù)調(diào)用是通過堆棧來進行的,調(diào)用前將參數(shù)壓棧冰肴,完事后將結(jié)果放入堆棧中屈藐,調(diào)用方在堆棧頂部拿結(jié)果。
接下來我們將lua面向?qū)ο蟮睦臃g成對應(yīng)的GopherLua代碼熙尉。也就是使用GopherLua提供的API一步一步組裝成復(fù)雜的lua對象定義及其實現(xiàn)联逻。
上面是一個簡單的Counter對象,提供incr和get兩個操作進行自增和獲取當(dāng)前值检痰。如果你不了解lua的面向?qū)ο筇匦园椋埶阉饕幌翷ua菜鳥教程進行閱讀
我們來把上面的lua代碼翻譯成一個等價的GopherLua代碼
換成了Go代碼就比上面的lua代碼復(fù)雜太多了,看起來也遠不及l(fā)ua直接铅歼。特別是返回值不是返回值公壤,而是返回值的個數(shù)换可,返回值要往棧里壓。還有參數(shù)也不是直接拿到的厦幅,而要從棧里面挨個拿沾鳄。函數(shù)調(diào)用在形式上像極了匯編語言。
GopherLua除了可以滿足基本的lua需要确憨,還將Go語言特有的高級設(shè)計直接移植到lua環(huán)境中译荞,使得內(nèi)嵌的腳本也具備了一些高級的特性
可以使用context.WithTimeout對執(zhí)行的lua腳本進行超時控制
可以使用context.WithCancel打斷正在執(zhí)行的lua腳本
多個lua解釋器實例之間還可以通過channel共享數(shù)據(jù)
支持多路復(fù)用選擇器select
使用Lua作為內(nèi)嵌腳本的另外一個重要優(yōu)勢在于Lua非常輕量級,占用內(nèi)存極小休弃。接下來我們使用下面的腳本來測試測試單個Lua解釋器實例占用的內(nèi)存大小吞歼。
上面的代碼開啟了10000個lua解釋器實例,每個解釋器實例調(diào)用一次斐波拉契函數(shù)輸出結(jié)果塔猾。然后在退出之前休眠100s便于我們使用top命令觀察進程的內(nèi)存占用篙骡。
觀察發(fā)現(xiàn)在筆者的mac電腦上,整個進程占據(jù)了大約1.7G左右的內(nèi)存丈甸。平攤下來大約每個解釋器實例占據(jù)170k左右的內(nèi)存空間糯俗,相比Python動輒幾個M大小的空間來說,這已經(jīng)非常節(jié)約了老虫,但實際上lua在節(jié)約內(nèi)存的道路上可以走的更遠叶骨。GopherLua提供了對Lua運行時進行裁剪的功能,這能使得它占用的內(nèi)存更小祈匙。
當(dāng)內(nèi)嵌腳本要被終端用戶使用時,需要考慮一些安全問題天揖。比如用戶編寫的腳本代碼使用了lua提供的庫函數(shù)訪問了不該訪問的文件夺欲,或者調(diào)用了一些不該調(diào)用的系統(tǒng)模塊。這些不良行為都會給系統(tǒng)帶來威脅今膊,需要進行約束些阅。
GopherLua可以創(chuàng)建一個非常干凈的Lua解釋器實例,不加載任何系統(tǒng)模塊斑唬。然后由程序員自己提供的模塊注冊進去市埋,給內(nèi)嵌腳本提供一個安全的沙箱運行環(huán)境。
閱讀相關(guān)文章恕刘,關(guān)注微信公眾號/知乎專欄/頭條號【碼洞】