Lua弱引用table


弱引用table

 

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

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

一個在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一個引用不應該阻礙對該對象的回收。所謂弱引用,就是一種會被垃圾收集器忽視的對象引用。如果一個對象的引用都是弱引用,該對象也會被回收,並且還可以以某種形式來刪除這些弱引用本身。

弱引用table有3種類型:

1、具有弱引用key的table;
2、具有弱引用value的table;
3、同時具有弱引用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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM