日期:2014.7.16
PartⅡ 17
Weak Tables and Finalizers

Lua实现的是自动的内存管理。程序可以创建对象,可是没有现成的函数来实现删除对象。Lua使用 garbage collection(垃圾回收机制?)来删除变成gargage的对象,这一特性带来了很大的便利,不再深陷于内存回收,并且可以避免很多因为内存回收而引发的一系列问题,如悬垂指针和内存泄漏。
本章节提到的Weak Tables和Finalizers是lua提供的一个特性,允许用户参与到lua的garbage collector机制中。Weak Table允许回收程序依旧在使用的对象,而finalizer则允许回收garbage collector没有完全或者说直接控制到的对象。

17.1 Weak Tables
garbage collector只会回收那些确定为garbage的对象,但是它推断不出用户认为哪些变量是garbage。lua中任何全局变量都不会是garbage,尽管程序没有再使用过这些变量Lua也不会自动回收。这也是这本书开篇讲到过的,全局变量在不使用的时候赋值为nil,这时系统才会自动回收内存。因此说Lua是推断不出用户的主观行为。

有的时候仅仅是清除了相关的引用是不够的,when you want to keep a collection of all live objects of some kind in your program.This task seems simple:all you have to do is to insert each new object into the collection.However ,once the object is part of the collection,it will never be collected.这段的意思到底是什么呢?我们需要做的仅是将新的对象插入到collection中,但是一旦我们这些对象成为了collection的一部分便再也不会被collected了。Lua并不知道这样的引用不能去阻止Lua对对象的回收,除非用户告诉Lua?

Weak table就是用来告知Lua某个引用不能去阻止Lua对某个对象的回收的机制。一个weak reference就是对一个garbage collector没有管理的对象的引用。如果所有指向某个对象的引用是weak的,那么这些对象将会被回收且这些引用也会被系统删除。weak table就是lua用来实现weak reference的,该table里面的存储的都是weak的。这就意味着,如果某个对象仅是受weak table控制,那么lua最终会回收这个对象。

Table拥有key和value,这两个值都可以是任何类型的对象。在一般情况下,garbage collector不会回收作为table的key或者value的对象,这就是说table的key和value都是强引用(strong reference),这就会影响lua回收他们所指向的对象。而在weak table中key和value都可以是weak的,这就意味着weak table会有三种类型:key是weak而value不是;value是weak的而key不是;key和value都是weak的。不管weak table是何种类型,只要key或者value被回收了那么整个table里面的内容都会被回收掉。

涉及到元表。元方法 __mode 赋予了table的弱特性,该方法的值类型为string类型。当值为"k",表示key是weak的;当值为"v",表示value是weak的;当值为"kv",则表示key和value都是weak的。

e.g.
a = {}
--此时表示key为weak
b = { __mode = "k"}
setmetatable(a,b)
key = {}
a[key] = 1
key = {}
a[key] = 2
collectgarbage()
for k,v in pairs(a) doprint(v)
end

在这个例子中,__mode的值是"k",表明这个table以key为weak,具体的例子中,对key的第二次赋值重写了key第一次复制时的引用,所以但进行内存回收时将第一次赋值的key回收了,而第二次赋值的没有。。。这里的key是一个table,是一个对象所以可以被回收。

要注意的是只能从weak table回收对象,而对于如numbers和booleans等变量,则不能回收。即假如我们以一个number作为tablekey,那么collector将不会移除这个key,当然当table的value是weak的,不管key是否是weak亦不管key的类型是不是对象,当value被回收了整个table里面的元素都会被移除了。
如果key是string类型这里需要特殊考虑:尽管string是可回收的,从实现角度看,其不像其余可回收的对象。像table和thread都是明确的创建的,如我们写a = {},就明确的创建了一个table。然而,"a" .. "b" 此时会创建一个string型变量嘛?假如此时系统中已经存在一个"ab"了怎么办?lua会继续创建一个嘛?编译器会在运行程序前就创建一个string型变量嘛?从程序员角度来看,string是变量而不是对象。因此,和number或boolean一样,string也不会从weak table中移除(除非value是weak的)。

17.2 Memoize Functions
记忆函数?

A common programming technique is to trade space for time?啥意思??(用空间换取时间??)能通过记住该函数的运算结果进而提升一个函数的运行效率,效率体现在当用同一个参数调用该函数的时候,直接返回已经记住的结果。这应该就是前文讨论的模块的设计思路--同一个模块一般情况下只会加载一次。

e.g.
local results = {}
function mem_loadstring( s )local res = results[s]     --从table中访问该参数if res == nil then     --如果该table中没有该值res = assert(load(s))results[s] = res      --将该值存入table中,下次访问的时候直接返回该值endreturn res
end

书上提到了存储这些可能会占用很大的空间,但是带来了效率的提升。这差不多是trade space for time 这句话的解释吧。有的时候,这也会带来不必要的浪费,比如说有些时候可能会以同一个参数频繁的调用某个函数,但是某些时候仅仅会调用一次,假如一直存储着这些信息这样就带来了不必要的浪费。一般情况下,上例中的results会累积存储每次以新参数调用该函数的信息,这样下去总会在某个时间点耗尽系统内存。此时上文提到的weak table就提供了解决方案。如果resultes中有weak的value,那么每次garbage-collection的回收就会移除在该回收点没有使用的value,这也意味着该results里面存储的信息都会被释放掉。

e.g.
local results = { }
--表示此时table中的value是weak的
setmetatable(results,{__mode = "v"})
function mem_loadstring( s )    <同上>
end

因为函数的参数是string型的(table的key),所以我们可以考虑将table设置为true weak的

e.g.
setmetatable(results,{__mode = "kv"})

结果是一样的。

这个机制也适合在我们想让某些对象是唯一值的情况。例如,用table表示颜色的时候,有三个字段red,green,blue,通常我们会这样创建

e.g.
function createRGB( r,g,b )return {red = r,green = g,blue = b}
end

在引入了我们现在讨论的这个机制后:

e.g.
local results = {}
setmetatable(results,{__mode = "v"})
function createRGB( r,g,b )local key = r .. "-" .. g .."-" b           --保持key的唯一性local color = results[key]if color == nil thencolor = {red = r,green = g,blue = b}results[key] = colorendreturn color
end

这样就保证每次以同参数创建的table都是同一个。引入了这一机制后,用户也可以直接比较通过两个color了,假如是同参数创建的那么就是同一个table,此时比较是相等的。否则就一定是不相等的。

17.3 Object Attributes
对象属性

另一个使用到了weak table的地方是将对象与其属性向关联起来。很多时候我们都需要将一些属性附加至对象上:函数的名字,table的默认值,数组的大小等等。
当对象是table的时候,我们可以将这些属性以一个特殊的key存储在自身这个table里面。如我们前文所采用的,最简单又是最唯一的key就是创建一个新的对象(通常是一个table)。但是当对象不是table的时候,这些属性就不能存储在自身,这个时候我们就需要采取别的方法来实现我们的要求了。
用额外的一个table使对象与其属性绑定起来,以对象为key,其属性为value。这个table将保存所有类型对象的属性,这也带来了困扰---不能回收这些对象了,因为这些对象被以key来使用。此时我们就需要引入weak key机制,使用weak key是考虑到,使用weak key不会影响系统回收那些没有被引用的对象;而从另一方面来考虑,如果是使用weak value,一旦value被回收了,与之相关联的对象也会被回收,这是我们不期望的。

17.4 Revisiting Tables with Default Values

我们已经讨论过如何实现创建一个带默认值的table。现在以我们在讨论的weak table来回顾一番这个主题。这里将会涉及到两个解决方案:object attributes and memorizing。
首先第一个方案:使用weak table来绑定table和它的默认值:

local defaults = {}
--设置defaults的key为weak
setmetatable(defaults,{__mode = "k"})
--在访问table元素的时候,如果没有该key则返回defaults的值,这里的参数是table,保持唯一性
local mt = {__index = function ( t )return defaults[t]
end}
--设置table的默认值,以table本身为defaults这个table的key
function setDefault( t,d )defaults[t] = dsetmetatable(t,mt)
end

这里如果defaults没有设置weak key,那么该table会将z在程序运行期间永久保存所有table的默认值。
此时假如我们这样操作:

e.g.
local a = {}
setDefault(a,1)
--那么我们访问一个a中不存在的元素
print(a.x)           --1 使用其默认值。

第二个方案:

e.g.
local metas = {}
--这里weak table设置value为weak的
setmetatable(metas,{__mode = "v"})
function setDefault( t,d )--每次从访问这个weak table,看是否有这个默认值的tablelocal mt = metas[d]if mt == nil then--如果没有则创建table作为t的元表mt = { __index = function (  )return dend}--以默认值为key保存这个元表metas[d] = mtend--设置t的元表,带默认参数d          setmetatable(t,mt)
end

在这里我们为每个不同的默认值设置不同的元表,但是我们会在每次使用同一个默认参数的时候复用同一个元表。
这里将value设置为weak的主要是为了能回收这些没有用到的元表。

针对不同情况,这两种方案有不同的性能表现。第一个方案需要为每个不同默认值的table准备内存空间(存储这些默认值);第二个方案则为不同的默认值准备空间(该方案以是否默认值不同而来设计的,即假如多个table共用一个默认值,那么此时只会存储一个值)。因此当我们的程序有数千个table但是只需要准备少数几个默认值,那么适合使用第二套方案;而如果table较少,所用的默认值也少,那么就适合使用第一套方案。

17.5 Ephemeron Tables
蜉蝣table??

设想这种情况:一个table其key是weak的,而其value又与其key相关联。

这种情况似乎是有可能的。例如,有一个常数函数构造工厂?,该函数接受一个对象参数并返回该对象的一个函数,无论何时访问这个函数都是返回该对象:

e.g.
function factory( o )return function ( ... )return oend
end
使用我们之前讨论的memorizing
dolocal mem = {}setmetatable(mem,{__mode = "k"})function factory( o )local res = mem[o]if not res thenres = function ( ... )return oendmem[o] = resendreturn resend
end

这样就不会每次都创建新的函数而增加开销,直接从mem这个weak table中寻找需要的信息。这一段的内容有点让人混淆:该table是key为weak的,而value不是,作者说value不会被collect,因为value是对每个function的强引用(这里指该factory)。之前提到的只要value或者key是weak的,一旦其中一个被collect了,那么该table里的都会被移除掉,书上说的是(whole entry disappears)难道指的是移除而不是被回收吗?
Lua5.2版本中提出了一个概念:ephemeron tables.指的是key是weak的,而value是strong的table。在ephemeron table中,key的可访问性影响着与之相关联的value的可访问性。The reference to v is only strong if there is some strong reference to k.如果对k有强引用那么对v也只能是强引用的,否则就会被移除,即便v直接或间接的引用了k。

17.6 Finalizers
Lua的garbage collector不仅可以用来收集lua的对象,同时也可以用来释放资源。现有多种语言提供finalizer的机制。finalizer指的是一个与一个对象相联系的当该对象要被collected时调用的一个函数:

e.g.
o = {x = "hi"}
setmetatable(o,{__gc = function (o )print(o.x)
end})
o = nil
collectgarbage()          --print hi

当我们调用collectgarbage()方法进行回收的时候,调用了与o关联的finalizer。
从上可以看得出,lua实现finalizer是通过设置元方法:__gc来实现的。
需要注意的:在设置元表的时候,需要先设置其元方法,也可以说是在设置元表前先标记对象。这点其实与之前讲元表-元方法的时候类似,如果先设置元表,再定义元方法其实lua是不会去执行我们定义的元方法的。因此假如上例这样实现:

e.g.o = {x = "hi"}
mt = {}
setmetatable(o,mt)
mt.__gc = function (o )print(o.x)
end
o = nil
collectgarbage()      --     这里不会打印任何东西,还可能引发不可预计的错误

而如果非要在设置完元表再设置元方法,可以先在元表内部给__gc 这个字段赋值(可以是任何类型)再在设置完元表后定义元方法:

e.g.o = {x = "hi"}
mt = {__gc = true}
setmetatable(o,mt)
mt.__gc = function (o )print(o.x)
end
o = nil
collectgarbage()      --hi 这样就能正确打印出来

lua的collector依据标记的顺序来处理一次finalize多个对象的finalizer

e.g.
mt = { __gc = function ( o )print(o[1])
end}
list = nil
for i=1,3 dolist = setmetatable({i,link = list},mt)
end
list = nil
collectgarbage()     -- 3  2  1

3是最后被标记的,所以最先被打印出来。

当调用一个finalizer的时候,该函数会调用标记的对象作为自己的参数。而其实此时该对象已经被回收掉了,而在该finalizer的函数体内实现了“复活”,因此在该finalizer结束执行前还是可以访问到作为其参数的对象的。“复活”这一特性是可以传递的:

e.g.
A = {x = "this is A"}
B = {f = A}
setmetatable(B,{__gc = function (o) print(o.f.x) end})
A,B = nil
collectgarbage()

这个例子很好的解释了传递这一特性,A已经被回收了,但是并没有设置finalizer,而B的一个value为A,B设置了finalizer。当A,B都被赋值为nil强制回收之后,在B的finalizer内部B实现了“复活”,而该特性传递给了B的valueA,与之相应的A也实现了复活。
因为“复活”这个机制的影响,对象被回收其实要经历两个阶段,第一个阶段回收器会对有finalizer的对象进行确认还没有调用它的finalizer,并“复活”该对象然后执行其finalizer,一旦该finalizer被执行了lua便会标记该对象为已经finalize了。第二个阶段回收器检测到该对象已经被finalize了,就会删除该对象。因此为了确保程序中所有的garbage都被回收了,需要强制调用collectgarbage这个函数两次。
因为lua会标记对象是否已经被finalize,所以对象的finalizer只会调用一次。如果直到程序运行结束某个对象都没有被回收,lua将会在整个lua的state被关闭之前调用其finalizer。

另一个有趣的机制是:可以实现每次当lua完成一个垃圾回收就调用一个给定的函数。这里的实现原理是,尽管finalizer只会实现一次,但是可以在每次执行的时候重新创建一个新的对象去运行下一个finalizer:

e.g.
dolocal mt = {__gc = function ( o )print("new cycle")setmetatable({},getmetatable(o))     --     每次执行finalizer就重新创建一个对象设置为同一个元表,同一个元方法end}setmetatable({},mt)
end
collectgarbage()
collectgarbage()
collectgarbage()

而对于拥有finalizer的对象和weak table之间的关系这里也需要讨论一番:回收器会在“复活”之前清理weak table的values,而其key则是在“复活”之后进行清理:

e.g.
wk = setmetatable({},{__mode = "k"})
wv = setmetatable({},{__mode = "v"})
o = {}
wv[1] = o;wk[o] = 10
setmetatable(o,{__gc = function ( o )print(wk[o],wv[1])
end})
o = nil
collectgarbage()     --10 nil
print(wk[o])            --nil

以上例子做出来很好的解释。wk其key是weak的,而wv其value是weak的。设置好元表之后,执行回收可以看到,打印出了10而没有打印出wv的元素,因为在垃圾回收之前wv就已经被清理了,而wk在回收之后清理。这也合理的解释了为什么我们使用weak key的table来存储对象的属性,因为设计中可能finalizer可能也需要访问这些属性。

转载于:https://www.cnblogs.com/zhong-dev/p/4044572.html

《Programming in Lua 3》读书笔记(十三)相关推荐

  1. OREILLY Programming .NET 3.5 读书笔记之一

    OREILLY Programming .NET 3.5 读书笔记之一 <Programming .NET 3.5>是OREILLY 2008.08出版的.NET 3.5 开发书籍,作者是 ...

  2. 《Programming in Scala》读书笔记(持续更新) - passover的个人空间 - DOIT博客 - 多易网...

    <Programming in Scala>读书笔记(持续更新) - passover的个人空间 - DOIT博客 - 多易网 <Programming in Scala>读书 ...

  3. 《Programming in Lua 3》读书笔记(一)

    断断续续的看这本书快一个月了,由于平时要上班所以读书时间是零碎的,再加上直接看的是英文版,而自己的英语水平就那样,所以进度不咋样.快一个月了,300来页的电子版至今才看到40来页.当然一开始我也没做很 ...

  4. 【读书笔记】语言基础- Lua语言入门(一)

    目录 注:本系列为<Lua程序设计-第4版> 的读书笔记,其中的见解有不完善的地方,可以在评论区指出,原版请看图书 Lua运行环境 一. 使用Lua语言解释器运行Lua语言:(下面的实例以 ...

  5. 《Java编程思想》读书笔记 第十三章 字符串

    <Java编程思想>读书笔记 第十三章 字符串 不可变String String对象是不可变的,每一个看起来会修改String值的方法,实际上都是创建一个全新的String对象,以及包含修 ...

  6. 【读书笔记】.NET本质论第四章-Programming with Type(Part Two)

    欢迎阅读本系列其他文章: [读书笔记].NET本质论第一章 The CLR as a Better COM [读书笔记].NET本质论第二章-Components(Part One) [读书笔记].N ...

  7. 《深入浅出DPDK》读书笔记(十三):DPDK虚拟化技术篇(加速包处理的vhost优化方案)

    Table of Contents 加速包处理的vhost优化方案 142.vhost的演进和原理 143.Qemu与virtio-net 144.Linux内核态vhost-net 145.用户态v ...

  8. 《The C Programming Language》读书笔记 说明

    <The C Programming Language>读书笔记 说明 作为笔记而言,完全是一种自写自看的行为,本来是没有必要写这篇东西的.但是作为一个生活在网络时代的学 生来说,想学好一 ...

  9. 【“计算机科学与技术”专业小白成长系列】SICP 读书笔记: The Elements of Programming

    SICP 读书笔记: The Elements of Programming 编程元素 强大的编程语言不仅仅是一种指示计算机执行任务的方法.语言也可以作为一个框架,我们可以在其中组织关于过程的想法.当 ...

最新文章

  1. Python能让你上天?带你挖掘隐藏彩蛋~(附代码)
  2. WinSock网络编程基础(3)server
  3. Source Code Collection for Reproducible Research
  4. 第三次学JAVA再学不好就吃翔(part114)--Properties类
  5. 【AtCoder】ARC078
  6. CentOS6 下Samba服务器的安装与配置
  7. java 不同类之间传递数据_java 数据在不同类之间的传递
  8. 05_DecisionTree_统计学习方法
  9. Java数据持久层框架 MyBatis之API学习五(Mapper XML 文件)
  10. android清理缓存动画、天气APP、购物下单选择器、阅读APP、饿了么demo等源码
  11. 通过js实现文字合成语音并播报
  12. opencv下载百度网盘链接及安装
  13. 蝌蚪网课助手mac_疫情期间如何录网课?(干货教程)手把手教你录出高质量网课。...
  14. ASP.Net之发展史
  15. java内存图解_java内存模型及GC原理 和 图解JVM在内存中申请对象及垃圾回收流程...
  16. android页面布局计算机,Android Studio制作简单计算器App
  17. 【OVS2.5.0源码分析】sFlow实现分析(3)
  18. 2010中国互联网哈哈榜
  19. 国外问卷调查这个项目可以做吗?
  20. SDIO2019R2游记&入坑2周年感想

热门文章

  1. 英文操作系统下WebBrowser控件无法显示本地页面的解决方法
  2. sql server的跨库查询(简单实现)
  3. 【hive】hive常见的几种文件存储格式与压缩方式的结合-------Parquet格式+snappy压缩 以及ORC格式+snappy压缩文件的方式
  4. 【组件】大数据框架安装功能来划分
  5. Lambda表达式改方法引用和构造器引用
  6. DateTimeFormatter,时间格式化与解析日期或时间
  7. 从入门到精通进阶篇 - 设置负载阶梯式压测场景(详解教程)
  8. php网页302错误,swfupload提示“错误302”的解决方法
  9. 学web前端开发写给新手的建议,超实用
  10. php java openssl ras_php基于openssl的rsa加密解密示例