lua定时器与定时任务的接口设计
来源:互联网 发布:织梦小说源码 编辑:程序博客网 时间:2024/06/05 22:31
在所有的服务器编程当中,定时任务永远是一个不可或缺的需求。
最直接的需求就是,每天凌晨0点0分的时候总是有一大堆的各种精力重置。
怎么来设计这个接口呢,想了几个方案:
- 每秒触发
- 每分钟触发
- 每整点触发
- 每天触发
- 每个月触发
oh no!不靠谱啊,如果这接口真设计成这样,得有多烂,灵光一现,unix下的crontab表达式非常完美的解决了这个问题。
附上crontab表达式的语法说明如下:
crontab特殊的符号说明:
"*"代表所有的取值范围内的数字。特别要注意哦!
"/"代表每的意思,如"*/5"表示每5个单位
"-"代表从某个数字到某个数字
","分散的数字
crontab文件的使用示例:
30 21 * * * 表示每晚的21:30
45 4 1,10,22 * * 表示每月1、10、22日的4 : 45
10 1 * * 6,0 表示每周六、周日的1 : 10
0,30 18-23 * * * 表示在每天18 : 00至23 : 00之间每隔30分钟
0 23 * * 6 表示每星期六的11 : 00 pm
* */1 * * * 每一小时
* 23-7/1 * * * 晚上11点到早上7点之间,每隔一小时
* 8,13 * * 1-5 从周一到周五的上午8点和下午1点
0 11 4 * mon-wed 每月的4号与每周一到周三的11点
0 4 1 jan * 一月一号的4点
看起来很复杂的样子,但其实够用就好,我们也不需要实现全部特性。
- 实现一个毫秒级别的定时器Update
- 根据这个update函数实现一个秒级别定时器
- 然后每秒取得自然时间与表达式中 分、时、几号、月份、星期几 分别匹配就可以实现了
- 由于定时器除了增加以外,可能还需要一个删除功能,那就再提供一个定时器命名的功能,用于增删改查定时器是本身
- 再加个测试函数。。完美
直接上代码:
--------------------------------------------
--任何一个记录产生一个实例
local Clock = {}
local Clock_mt = {__index = Clock}
local
function
__checkPositiveInteger(name, value)
if
type(value) ~=
"number"
or value < 0 then
error(name ..
" must be a positive number"
)
end
end
--验证是否可执行
local
function
__isCallable(callback)
local tc = type(callback)
if
tc ==
'function'
then
return
true
end
if
tc ==
'table'
then
local mt = getmetatable(callback)
return
type(mt) ==
'table'
and type(mt.__call) ==
'function'
end
return
false
end
local
function
newClock(cid, name, time, callback, update, args)
assert(time)
assert(callback)
assert(__isCallable(callback),
"callback must be a function"
)
return
setmetatable({
cid = cid,
name = name,
time = time,
callback = callback,
args = args,
running = 0,
update = update
}, Clock_mt)
end
function
Clock:reset(running)
running = running or 0
__checkPositiveInteger(
'running'
, running)
self.running = running
self.deleted = nil --如果已经删除的,也要复活
end
local
function
updateEveryClock(self, dt)
__checkPositiveInteger(
'dt'
, dt)
self.running = self.running + dt
while
self.running >= self.time
do
self.callback(unpack(self.args))
self.running = self.running - self.time
end
return
false
end
local
function
updateAfterClock(self, dt) -- returns
true
if
expired
__checkPositiveInteger(
'dt'
, dt)
if
self.running >= self.time then
return
true
end
self.running = self.running + dt
if
self.running >= self.time then
self.callback(unpack(self.args))
return
true
end
return
false
end
local
function
match( left, right )
if
left ==
'*'
then
return
true
end
--单整数的情况
if
'number'
== type(left) and left == right then
return
true
end
--范围的情况 形如 1-12/5,算了,先不支持这种每隔几分钟的这种特性吧
_,_,a,b = string.find(left,
"(%d+)-(%d+)"
)
if
a and b then
return
(right >= tonumber(a) and right <= tonumber(b))
end
--多选项的情况 形如 1,2,3,4,5
--哎,luajit不支持gfind,
--
for
d
in
string.gfind(left,
"%d+"
)
do
--其实也可以
for
i
in
string.gmatch(left,
'(%d+)'
)
do
local pos = 0
for
st,sp
in
function
()
return
string.find(left,
','
, pos,
true
) end
do
if
tonumber(string.sub(left, pos, st - 1)) == right then
return
true
end
pos = sp + 1
end
return
tonumber(string.sub(left, pos)) == right
end
local
function
updateCrontab( self, dt )
local now = os.date(
'*t'
)
local tm = self.time
--print(
'updateCrontab/now:'
, now.min, now.hour, now.day, now.month, now.wday)
--print(
'updateCrontab/tm'
, tm.mn, tm.hr, tm.day, tm.mon, tm.wkd)
--print(
'match:'
,match(tm.mn, now.min), match(tm.hr, now.hour), match(tm.day, now.day), match(tm.mon, now.month), match(tm.wkd, now.wday))
if
match(tm.mn, now.min) and match(tm.hr, now.hour)
and match(tm.day, now.day) and match(tm.mon, now.month)
and match(tm.wkd, now.wday)
then
--print(
'matching'
,self.name,self.callback,self.running)
self.callback(unpack(self.args))
self.running = self.running + 1
end
return
false
end
--遍历并执行所有的定时器
local
function
updateClockTables( tbl )
for
i =
#tbl, 1, -1 do
local v = tbl[i]
if
v.deleted ==
true
or v:update(1) then
table.remove(tbl,i)
end
end
end
----------------------------------------------------------
local crontab = {}
crontab.__index = crontab
function
crontab.
new
( obj )
local obj = obj or {}
setmetatable(obj, crontab)
--执行一下构造函数
if
obj.ctor then
obj.ctor(obj)
end
return
obj
end
function
crontab:ctor( )
--所有的定时器
self._clocks = self._clocks or {}
self._crons = self._crons or {}
--累积的时间差
self._diff = self._diff or 0
--已命名的定时器,设置为弱引用表
self._nameObj = {}
setmetatable(self._nameObj, {__mode=
"k,v"
})
--取得现在的秒数,延迟到整点分钟的时候启动一个定时
self:after(
"__delayUpdateCrontab"
, 60-os.time()%60,
function
( )
--在整点分钟的时候,每隔一分钟执行一次
self:every(
"__updateCrontab"
, 60,
function
( )
updateClockTables(self._crons)
end)
end)
end
function
crontab:update( diff )
self._diff = self._diff + diff
while
self._diff >= 1000
do
--TODO:这里真让人纠结,要不要支持累积时间误差呢?
self._diff = self._diff - 1000
--开始对所有的定时器心跳,如果返回
true
,则从列表中移除
updateClockTables(self._clocks)
end
end
function
crontab:remove( name )
if
name and self._nameObj[name] then
self._nameObj[name].deleted =
true
end
end
--通过判断callback的真正位置,以及参数类型来支持可变参数
--返回值顺序 number, string, number,
function
, args
--总的有如下5种情况
--1) cid,name,time,callback,args
--2) name,cid,time,callback,args
--3) name,time,callback,args
--4) cid,time,callback,args
--5) time,callback,args
local
function
changeParamsName( p1, p2, p3, p4, p5 )
if
__isCallable(p4) then
if
type(p1) ==
'string'
then
return
p2,p1,p3,p4,p5
else
return
p1,p2,p3,p4,p5
end
elseif __isCallable(p3) then
if
type(p1) ==
'string'
then
return
nil,p1,p2,p3,p4
else
return
p1,nil,p2,p3,p4
end
else
return
nil,nil,p1,p2,p3
end
end
function
crontab:every( cid, name, time, callback, args )
--支持可变参数
cid, name, time, callback, args = changeParamsName(cid, name, time, callback,args)
__checkPositiveInteger(
'time'
, time)
local clock = newClock(cid, name, time, callback, updateEveryClock, args or {})
table.insert(self._clocks,clock)
if
name and name ~=
''
then
self._nameObj[name] = clock
end
return
clock
end
function
crontab:after( cid, name, time, callback, args )
cid, name, time, callback, args = changeParamsName(cid, name, time, callback,args)
__checkPositiveInteger(
'time'
, time)
local clock = newClock(cid, name, time, callback, updateAfterClock, args or {})
table.insert(self._clocks,clock)
if
name and name ~=
''
then
self._nameObj[name] = clock
end
return
clock
end
--增加计划任务,精度到达分钟级别
--表达式:分钟[0-59] 小时[0-23] 每月的几号[1-31] 月份[1-12] 星期几[1-7]
-- 星期天为1,
--
"*"
代表所有的取值范围内的数字
--
"-"
代表从某个数字到某个数字
--
"/"
代表每的意思,如
"*/5"
表示每5个单位,未实现
--
","
分散的数字
-- 如:
"45 4-23/5 1,10,22 * *"
function
crontab:addCron(cid, name, crontab_str, callback, args )
cid, name, crontab_str, callback, args = changeParamsName(cid, name, crontab_str, callback, args)
--print(cid, name, crontab_str, callback)
local t = {}
for
v
in
string.gmatch(crontab_str,
'[%w._/,%-*]+'
)
do
--如果可以转成整型直接转了,等下直接对比
local i = tonumber(v)
table.insert(t, i and i or v)
end
if
table.getn(t) ~= 5 then
return
error(string.format(
'crontab string,[%s] error!'
,crontab_str))
end
local time = {mn = t[1], hr = t[2], day = t[3], mon = t[4], wkd = t[5]}
local clock = newClock(cid, name, time, callback, updateCrontab, args or {})
table.insert(self._crons,clock)
if
name and name ~=
''
then
self._nameObj[name] = clock
end
end
return
crontab
再看看测试代码:
--传说中的测试代码
local
function
RunTests()
-- the following calls are equivalent:
local
function
printMessage(a )
print(
'Hello'
,a)
end
local cron = crontab.
new
()
local c1 = cron:after( 5, printMessage)
local c2 = cron:after( 5, print, {
'Hello'
})
c1:update(2) -- will print nothing, the action is not done yet
c1:update(5) -- will print
'Hello'
once
c1:reset() -- reset the counter to 0
-- prints
'hey'
5 times and then prints
'hello'
while
not c1:update(1)
do
print(
'hey'
)
end
-- Create a periodical clock:
local c3 = cron:every( 10, printMessage)
c3:update(5) -- nothing (total time: 5)
c3:update(4) -- nothing (total time: 9)
c3:update(12) -- prints
'Hello'
twice (total time is now 21)
-------------------------------------
c1.deleted =
true
c2.deleted =
true
c3.deleted =
true
------------------------------
--测试一下match
print(
'----------------------------------'
)
assert(match(
'*'
,14) ==
true
)
assert(match(
'12-15'
,14) ==
true
)
assert(match(
'18-21'
,14) ==
false
)
assert(match(
'18,21'
,14) ==
false
)
assert(match(
'18,21,14'
,14) ==
true
)
--加一个定时器1分钟后执行
cron:update(1000)
--加入一个定时器每分钟执行
cron:addCron(
'每秒执行'
,
'* * * * *'
, print, {
'.......... cron'
})
cron:update((60-os.time()%60)*1000)
cron:update(30*1000)
cron:update(31*1000)
cron:update(1)
cron:update(60*1000) --打印两次
end
也可以直接到 https://github.com/linbc/crontab.lua 下载代码
参考资料:
http://www.cise.ufl.edu/~cop4600/cgi-bin/lxr/http/source.cgi/commands/simple/cron.c
https://github.com/kikito/cron.lua
- lua定时器与定时任务的接口设计
- java定时器与定时任务
- 第24章 任务中定时器的设计与实现
- 基于SpringMvc的定时任务设计与踩的坑
- Web后台任务定时执行工具的设计与实现
- 小码农的代码(四)----------JAVA中Timer定时器与Spring定时任务
- java Quartz定时器任务与Spring task定时的几种实现
- java Quartz定时器任务与Spring task定时的几种实现,
- java Quartz定时器任务与Spring task定时的几种实现
- java Quartz定时器任务与Spring task定时的几种实现
- quick-lua中定时器的设计
- 有关于定时器(定时任务)
- Java定时器实现定时任务
- Spring 定时器 定时执行任务
- 定时任务接口ScheduledExecutorService
- C# 使用定时任务 之 谈谈定时器(Timer)的使用
- Java 定时器的使用:每天定时执行任务
- 定时器Timer和定时任务TimerTask的用法
- 蓝鸥零基础学习HTML5第五讲 CSS的基础样式
- Remix OS 安装及初次使用心得
- 学习tomcat之通过shell批量管理多个tomcat
- Unity3D follow.
- iOS APP图标一键生成
- lua定时器与定时任务的接口设计
- 使用随机算法产生一个数,要求把1-1000W之间这些数全部生成。
- 15章 上机1
- 123. Best Time to Buy and Sell Stock III(dp)
- sha1签名
- 15章 上机4商品批发显示总金额
- [深度学习论文笔记][Weight Initialization] Delving deep into rectifiers: Surpassing human-level performance
- 如何判断用户是拒绝还是允许通讯录权限?
- java练习--判断字符出现次数