最近在重构自己写的框架中的定时器模块,需要把回调函数保存起来,大概如下:
function timer_mgr:save_timer( this,callback ) return { this = this,callback = callback} end -- 创建新定时器 -- @after:延迟N秒后启动定时器 -- @repeated:N秒循环 -- @this:回调对象 -- @callbakck:回调函数 function timer_mgr:new_timer( after,repeated,this,callback ) local timer_id = self:next_id() self.timers[timer_id] = save_timer( this,callback ) end function timer_mgr:do_timer( timer_id ) local timer = self.timers[timer_id] -- 调用之前保存的定时器回调 return timer.callback( timer.this ) end
正常情况下,用table保存定时器的回调参数,毕竟lua中也没有太多的数据结构可以选择。不过,我们也可以这样用closure来保存:
function timer_mgr:save_timer( this,callback ) return function() return callback( this ) end end -- 创建新定时器 -- @after:延迟N秒后启动定时器 -- @repeated:N秒循环 -- @this:回调对象 -- @callbakck:回调函数 function timer_mgr:new_timer( after,repeated,this,callback ) local timer_id = self:next_id() self.timers[timer_id] = save_timer( this,callback ) end function timer_mgr:do_timer( timer_id ) local timer = self.timers[timer_id] -- 调用之前保存的定时器回调 return timer() end
这样似乎看起来更优雅更方便一些,不过,频繁创建closure也是很消耗内存和cpu的,需要和table对比一下:
function test_table_object( this,cb ) return { this = this,cb = cb } end function test_closure_object( this,cb ) return function() return cb( this ) end end local function cb() print("do nothing ...") end local max = 1000000 local table_mgr = {} local closure_mgr = {} function test_table() local beg_m = collectgarbage("count") local beg_tm = os.clock() for idx = 1,max do table_mgr[idx] = test_table_object( {},cb ) end local end_m = collectgarbage("count") local end_tm = os.clock() print("table test",end_m - beg_m,end_tm - beg_tm) end function test_closure() local beg_m = collectgarbage("count") local beg_tm = os.clock() for idx = 1,max do table_mgr[idx] = test_closure_object( {},cb ) end local end_m = collectgarbage("count") local end_tm = os.clock() print("closure test",end_m - beg_m,end_tm - beg_tm) end collectgarbage("stop") collectgarbage("collect") test_closure() test_table()
这段代码,分别在win10 - I3 cpu和debian7(虚拟机) - A8 cpu下测试:
closure test 117946.5 0.489 table test 125000.0 0.446 closure test 180446.5 0.75 table test 171875.0 0.72
可以看到,table和closure的消耗其实都差不多。但closure在这个应用场景下就优雅得多,因为可以很方便地传更多的参数,比如:
function timer_mgr:save_timer( this,callback,... ) return function() return callback( this,... ) end end function timer_mgr:new_timer( after,repeated,this,callback,... ) local timer_id = self:next_id() self.timers[timer_id] = save_timer( this,callback,... ) end function timer_mgr:do_timer( timer_id ) local timer = self.timers[timer_id] -- 调用之前保存的定时器回调 return timer() end
而table要传可变参则不太好处理了,需要另外创建一个table来pack参数,调用时再unpack,或者只用一个table把callback函数和参数存一起,调用时把函数移除再unpack。总而言之,在这种需要保存参数的回调,closure更合适。当然,如果你的回调函数不带参数,那就是另外一码事了。
查了下,之前也有人做了类似的测试,结果也差不多:http://lua-users.org/wiki/ObjectOrientationClosureApproach