Lua弱引用table

弱引用table

 

與python等腳本語言相似地,Lua也採用了自動內存管理(Garbage Collection),一個程序只需建立對象,而無需刪除對象。經過使用垃圾收集機制,Lua會自動刪除過時對象。垃圾回收機制能夠將程序員從C語言中常出現的內存泄漏、引用無效指針等底層bug中解放出來。html

咱們知道Python的垃圾回收機制使用了引用計數算法,當指向一個對象的全部名字都失效(超出生存期或程序員顯式del了)了,會將該對象佔用的內存回收。但對於循環引用是一個特例,垃圾收集器一般沒法識別,這樣會致使存在循環引用的對象上的引用計數器永遠不會變爲零,也就沒有機會被回收。python

一個在python中使用循環引用的例子:程序員

class main1:
    def __init__(self):
        print('The main1 constructor is calling...')
    def __del__(self):
        print('The main1 destructor is calling....')

class main2:
    def __init__(self, m3, m1):
        self.m1 = m1
        self.m3 = m3
        print('The main2 constructor is calling...')
    def __del__(self):
        print('The main2 destructor is calling....')

class main3:
    def __init__(self):
        self.m1  = main1()
        self.m2 = main2(self, self.m1)
        print('The main3 constructor is calling...')
    def __del__(self):
        print('The main3 destructor is calling....')

# test
main3()
        

輸出內容爲:算法

The main1 constructor is calling...
The main2 constructor is calling...
The main3 constructor is calling...

能夠看出,析構函數(__del__函數)沒有被調用,循環引用致使了內存泄漏。數組

 

垃圾收集器只能回收那些它認爲是垃圾的東西,不會回收那些用戶認爲是垃圾的東西。好比那些存儲在全局變量中的對象,即便程序不會再用到它們,但對於Lua來講它們也不是垃圾,除非用戶將這些對象賦值爲nil,這樣它們才能被釋放。但有時候,簡單地清除引用還不夠,好比將一個對象放在一個數組中時,它就沒法被回收,這是由於即便當前沒有其餘地方在使用它,但數組仍引用着它,除非用戶告訴Lua這項引用不該該阻礙此對象的回收,不然Lua是無從得知的。服務器

table中有key和value,這二者均可以包含任意類型的對象。一般,垃圾收集器不會回收一個可訪問table中做爲key或value的對象。也就是說,這些key和value都是強引用,它們會阻止對其所引用對象的回收。在一個弱引用table中,key和value是能夠回收的。函數

弱引用table(weak table)是用戶用來告訴Lua一個引用不該該阻礙對該對象的回收。所謂弱引用,就是一種會被垃圾收集器忽視的對象引用。若是一個對象的引用都是弱引用,該對象也會被回收,而且還能夠以某種形式來刪除這些弱引用自己。lua

弱引用table有3種類型:spa

一、具備弱引用key的table;
二、具備弱引用value的table;
三、同時具備弱引用key和value的table;指針

table的弱引用類型是經過其元表中的__mode字段來決定的。這個字段的值應爲一個字符串:
若是包含'k',那麼這個table的key是弱引用的;
若是包含'v',那麼這個table的value是弱引用的;

 

弱引用table的一個例子,這裏使用了collectgarbage函數強制進行一次垃圾收集:

a = {1,4, name='cq'}

setmetatable(a, {__mode='k'})

key = {}
a[key] = 'key1'

key = {}
a[key] = 'key2'

print("before GC")
for k, v in pairs(a) do
    print(k, '\t', v)
end

collectgarbage()

print("\nafter GC")
for k, v in pairs(a) do
    print(k, '\t', v)
end

輸出:

before GC
1                       1
2                       4
table: 0x167ba70                        key1
name                    cq
table: 0x167bac0                        key2

after GC
1                       1
2                       4
name                    cq
table: 0x167bac0                        key2

在本例中,第二句賦值key={}會覆蓋第一個key,當收集器運行時,因爲沒有地方在引用第一個key,所以第一個key就被回收了,而且table中的相應條目也被刪除了。至於第二個key,變量key仍引用着它,所以它沒有被回收。

注意,弱引用table中只有對象能夠被回收,而像數字、字符串和布爾這樣的「值」是不可回收的。

 

 

備忘錄(memoize)函數是一種用空間換時間的作法,好比有一個普通的服務器,每當它收到一個請求,就要對代碼字符串調用loadstring,而後再調用編譯好的函數。不過,loadstring是一個昂貴的函數,有些發給服務器的命令有很高的頻率,例如"close()",若是每次收到一個這樣的命令都要調用loadstring,那還不如讓服務器用一個輔助的table記錄下全部調用loadstring的結果。

備忘錄函數的例子:

local results = {}

setmetatable(results, {__mode='v'})

function mem_loadstring(s)

    local res = results[s]

    if res == nil then
        res=assert(loadstring(s))
        results[s]=res
    end

    return res
end 

local a = mem_loadstring("print 'hello'")
local b = mem_loadstring("print 'world'")

a = nil

collectgarbage()

for k,v in pairs(results) do
    print(k, '\t', v)
end

例子中,table results會逐漸地積累服務器收到的全部命令及其編譯結果。通過必定時間後,會耗費大量的內存。弱引用table正好能夠解決這個問題,若是results table具備弱引用的value,那麼每次垃圾收集都會刪除全部在執行時未使用的編譯結果。

 

lua元表一文中,提到過如何實現具備默認值的table。若是要爲每個table都設置一個默認值,又不想讓這些默認值持續存在下去,也可使用弱引用table,以下面的例子:

local defaults = {}

setmetatable(defaults, {__mode='k'})

local mt = {__index=function(t) return defaults[t] end}

function setDefault(t, d)
    defaults[t] = d
    setmetatable(t, mt)
end 


local a = {}
local b = {}

setDefault(a, "hello")
setDefault(b, "world")

print(a.key1)
print(b.key2) 

b = nil
collectgarbage()

for k,v in pairs(defaults) do
    print(k,'\t',v)
end
相關文章
相關標籤/搜索