zoukankan      html  css  js  c++  java
  • 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, '	', v)
    end
    
    collectgarbage()
    
    print("
    after GC")
    for k, v in pairs(a) do
        print(k, '	', 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, '	', 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,'	',v)
    end
  • 相关阅读:
    在Ubuntu下编译Qt错误及处理办法
    二、数字电路中常见器件应用分析-三极管
    2.NB-IoT及通信协议
    1.编写一个shell脚本
    7.STM32中GPIO理解
    (引用)!Unicode,GBK以及UTF8的联系和区别
    java垃圾回收
    java集合框架概述
    RSA实现(java)
    RSA算法(非对称加密)
  • 原文地址:https://www.cnblogs.com/chenny7/p/4050259.html
Copyright © 2011-2022 走看看