记录——时间轮定时器(lua 实现)

http://www.cnblogs.com/mmc1206x/p/6849172.html

 很长一段时间里,我错误的认识了定时器。无意中,我发现了“时间轮”这个名词,让我对定时器有了新的看法。

  我错误的认为,定时器只需要一个 tick 队列,按指定的时间周期遍历队列,检查 tick 倒计时满足触发条件就触发回调。

tick 定义如下:

1 struct Tick {
2     int_t n;
3     func_t func;
4 };

遍历触发实现如下:

 1 void Update()
 2 {
 3     for (auto & tick: _ticks)
 4     {
 5         if (Check(tick))
 6         {
 7             tick.func();
 8             Remove(tick);
 9         }
10     }
11 }

实现很简洁,但效率却出奇的慢。

假设有100个tick,依次触发时间是100~10000毫秒,也就是每一个tick的触发间隔为100毫秒

可以想象,在头100毫秒内,不会有任何tick被触发,但是Update却傻乎乎对100个tick进行Check。

当时间达到100毫秒的时候,只有第一个 tick 达到了触发条件,但是Update依旧会对余下99个进行Check。

时间轮很好的解决了这个问题。

思路是这样的:

需要一个轮盘,轮盘上有若干个插槽,

把 tick 放进合适的插槽,

每次轮询直接触发插槽里的 tick。

假设要实现一个最低刻度为毫秒,最大上限为1天的定时器(最长延时23点59分59秒999毫秒)

假设要在 3点30分25秒600毫秒 处安插一个 tick。

首先这个轮盘需要 24 × 60 x 60 x 1000 个插槽,

其次把 3点30分25秒600毫秒 转化为定时器最低刻度(毫秒),也就是 11005600 = 600 + 25000 + 180000 + 10800000.

也就是说,在这个轮盘的 11005600 刻度位置,安插上这个 tick。

就是这么简单粗暴!

这是一个错误例子。

其实不需要那么多插槽,如果你见过水表,你应该知道该怎么做,继续前面的假设。

我们为每一个时间单位准备一个时间轮,就是 时(24),分(60),秒(60),毫秒(1000)

因此只需要 1144 = 24 + 60 + 60 + 1000 个插槽就够了。

在 3点30分25秒600毫秒 处安插一个 tick,

首先在 时(3) 安插上这个 tick,

当执行到 时(3) 的时候,删除这个 tick,检查到该 tick 还有 30分25秒600毫秒

于是在 分(30)安插上这个 tick,

当执行到 分(30)的时候,删除这个 tick,检查到该 tick 还有 25秒600毫秒

于是在 秒(25)安插上这个 tick,

当执行到 秒(25)的时候,删除这个tick,检查到该 tick 还有 600毫秒

于是在 毫秒(600)安插上这个 tick,

当执行到 毫秒(600)的时候,删除这个tick,触发这个 tick。

这个 tick 从被安插到被触发,总共只需要 Check(4) 次。

如果采用本文开头的思路,那将会被 Check(天文数字)次。

因为只是为了理解算法,我只是用lua实现了一遍,算法本身大概只有90行不到,吐个槽,lua索引从1开始很蛋疼。

 1 sformat = string.format
 2 tinsert = table.insert
 3 tremove = table.remove
 4 tconcat = table.concat
 5 mfloor = math.floor
 6 local utils = require("utils")
 7 local _M = {    _slots = nil,
 8                 _cycle = nil,    }
 9
10 function _M.Init(self, cycle)
11     if not self._slots then
12         self._slots = {}
13         self._slots[1] = {}
14         self._slots[2] = {}
15         self._slots[3] = {}
16         self._slots[4] = {}
17         utils.tinsert_n(self._slots[1], {}, 24)
18         utils.tinsert_n(self._slots[2], {}, 60)
19         utils.tinsert_n(self._slots[3], {}, 60)
20         utils.tinsert_n(self._slots[4], {}, 1000)
21     end
22     if not self._cycle then
23         self._cycle = cycle
24     end
25 end
26
27 function _M.Update(self, cycle)
28     local h1, m1, s1, ms1 = utils.ms2t(self._cycle)
29     self._cycle = cycle
30     local h2, m2, s2, ms2 = utils.ms2t(self._cycle)
31     self:__UpdateT__(24, 1, h1, h2, utils.bind(self.__UpdateH__, self))
32     self:__UpdateT__(60, 2, m1, m2, utils.bind(self.__UpdateM__, self))
33     self:__UpdateT__(60, 3, s1, s2, utils.bind(self.__UpdateS__, self))
34     self:__UpdateT__(1000, 4, ms1, ms2, utils.bind(self.__UpdateMS__, self))
35 end
36
37 function _M.AddTimer(self, delay, func)
38     self:__Insert__(delay + 1, func)
39 end
40
41 function _M.__Insert__(self, delay, func)
42     if 0 == delay then
43         func()
44     else
45         local h1, m1, s1, ms1 = utils.ms2t(delay)
46         local h2, m2, s2, ms2 = utils.ms2t(delay + self._cycle)
47         local tick = {    func = func,
48                         time = { h = h2, m = m2, s = s2, ms = ms2 } }
49         if h1 ~= 0 then
50             tinsert(self._slots[1][h2 == 0 and 24 or h2], tick)
51         elseif m1 ~= 0 then
52             tinsert(self._slots[2][m2 == 0 and 60 or m2], tick)
53         elseif s1 ~= 0 then
54             tinsert(self._slots[3][s2 == 0 and 60 or s2], tick)
55         elseif ms1 ~= 0 then
56             tinsert(self._slots[4][ms2 == 0 and 1000 or ms2], tick)
57         end
58     end
59 end
60
61 function _M.__UpdateT__(self, cycle, index, first, last, func)
62     local slots = self._slots[index]
63     while first ~= last do
64         first = first + 1
65         for i = 1, #slots[first] do
66             func(slots[first][i])
67         end
68         slots[first] = {}
69         first = first % cycle
70     end
71 end
72
73 function _M.__UpdateH__(self, v)
74     self:__Insert__(utils.t2ms(0, v.time.m, v.time.s, v.time.ms), v.func)
75 end
76
77 function _M.__UpdateM__(self, v)
78     self:__Insert__(utils.t2ms(0, 0, v.time.s, v.time.ms), v.func)
79 end
80
81 function _M.__UpdateS__(self, v)
82     self:__Insert__(utils.t2ms(0, 0, 0, v.time.ms), v.func)
83 end
84
85 function _M.__UpdateMS__(self, v)
86     self:__Insert__(utils.t2ms(0, 0, 0, 0), v.func)
87 end
88
89 return _M

源码下载

时间: 2024-10-05 06:13:25

记录——时间轮定时器(lua 实现)的相关文章

浅析时间轮定时器

前言: 最早是看到skynet群里边有人问如何取消定时器的问题,那时候正好在研读skynet代码,于是决定试试.但是最终只在lua层面实现了一个伪取消定时器的方案,而且还是不是优解. 云风说从c层面取消定时器的开销要大于从lua层面取消的开销,当时不知道为什么. 最近研读了云风实现的时间轮定时器代码(看着相当费劲啊),  过程中网上搜了很多资料,但大部分没能帮助我有个更好的理解,所以打算从写篇文章,希望能帮助像我一样的newbee, 更好的理解时间轮定时器. 这里不讲定时器的进化过程,只讲时间轮

原 荐 简单说说Kafka中的时间轮算法

零.时间轮定义 简单说说时间轮吧,它是一个高效的延时队列,或者说定时器.实际上现在网上对于时间轮算法的解释很多,定义也很全,这里引用一下 朱小厮博客 里出现的定义: 参考下图,Kafka中的时间轮(TimingWheel)是一个存储定时任务的环形队列,底层采用数组实现,数组中的每个元素可以存放一个定时任务列表(TimerTaskList).TimerTaskList是一个环形的双向链表,链表中的每一项表示的都是定时任务项(TimerTaskEntry),其中封装了真正的定时任务TimerTask

高性能定时器时间轮的探究

时间轮的概念 关于定时器有很多种,有基于升序的定时器时间链表,但是这种链表存在效率的不足,就是当插入定时器的时候时间复杂度是O(n).今天,我们来认识一下高性能定时器时间轮. 如上图所示,就是一个时间轮的基本轮廓.一个轮子上有很多槽slot,每一个槽指向一个定时器链表,这个链表是无序的.时间轮每转动一步就会指向下一个槽,其实也可以理解为一个滴答时间成为时间轮的槽间隔si (slot interval).它实际上就是心跳时间.如果该时间轮有n个槽,因此它运转一周的时间是n*si. 如果现在指针指向

时间轮算法

问题引入:游戏里面每个Player身上有很多buffs,在每一个tick(最小时间段)都要去检查buff里面的每一个buff是不是过期,产生的效果如何,造成在每个tick里面都去遍历一个长list,明显很不好. 怎么优化? 1.原始模型:    buff的状态在每一个tick里面都要更新!可以想象指针每移动一下,都会非常沉重地拖着所有的BuffList,好可怕-- 2. 优化模型1:   我们要避免的是:原始模型在每一个tick里面都要遍历List,那么我们试下以Times为key,在加入buf

时间轮

老早之前就听说时间轮算法特别高效,Linux内核都用的它,这两天抽空实现了遍--嗯,被差一bug搞死(~ ̄▽ ̄~) 啊哈 网上扣来的图,原理好懂:轮子里的每格代表一小段时间(精度),连起来就能表示时间点了(我去年买了个表),格子内含链表,中存回调函数:时间指针每次转动一格,指向某格时,取出链表里的回调函数依次执行,后清空链表,等待下一次转动. 加入节点逻辑也简单:在轮子可表示的时间范围内(格子数*格子精度),配合当前时针位置,对格子总数取余,即得本节点需放哪个格子. 进一步为扩大时间轮的表示范围

Linux C++ 实现时间轮 优化超时检测机制

参考资料: http://www.ijilei.com/8357 https://www.zhihu.com/question/38427301 https://www.ibm.com/developerworks/cn/linux/l-cn-timers/ http://www.cnblogs.com/processakai/archive/2012/04/11/2442294.html 思路和代码的编写主要是参考的csdn上的一个java的代码 http://blog.csdn.net/mi

产品线上问题记录一:启动页仅记录时间未检查升级,导致不能弹出自动更新弹窗

记软件测试线上问题一: 启动页仅记录时间未检查升级,导致不能弹出自动更新弹窗 上线日期: V1.2.0 2016年12月20日 V1.3.1 2017年2月23日 问题经过描述: 最新版本集成了一个文件,所以领导要运营数据,然后发现不弹自动升级弹窗,只能手动更新,查看版本使用用户的时候发现近50%用户在使用老版本V1.2.0,并未升级,数据如下: 1.0版本数量168,1.1.0版本数量6562,1.1.1版本数量9286,1.2.0版本数量19679,1.3.0版本数量2829,1.3.1版本

时间轮(TimeWheel)的设计与实现

一.前言 由于工作的需要,得实现一个用于控制事件超时抛弃的时间轮,由于这是一个相对独立的接口,就总结分享一下. 首先看下需求,此时间轮需要具备下面几个功能: 1)能添加事件,同时附上其超时时间: 2)如果事件正常执行结束,可以显示将其从时间轮上剔除掉,而不需要等时间轮自动移除: 3)如果事件到了设定的超时时间还没执行完,则时间轮需将其剔除掉,并发送一个超时的消息给系统. 基于这样的需求,下面就进行相应的设计和实现. 二.时间轮的设计 基于前面的需求,可以抽象出两个实体来:时钟和槽,其中时钟去负责

Unity记录时间

记录时间 在Unity中记录时间需要用到 Time 类.Time类中比较重要的变量为 deltaTime(只读),它指的是从最近一次调用Update 或者 FixedUpdate 方法到现在的时间. 如果想均匀的旋转一个物体,不考虑帧率的情况下,可以乘以 Time.DataTime .具体操作时可以使用如下代码 using UnityEngine; //引入系统包 using System.Collections; //声明类 public class NewBehaviourScript {