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

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

tick 定义如下:

 struct Tick {
     int_t n;
     func_t func;
 };

遍历触发实现如下:

 void Update()
 {
     for (auto & tick: _ticks)
     {
         if (Check(tick))
         {
             tick.func();
             Remove(tick);
         }
     }
 }

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

假设有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。

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

 sformat = string.format
 tinsert = table.insert
 tremove = table.remove
 tconcat = table.concat
 mfloor = math.floor
 local utils = require("utils")
 local _M = {    _slots = nil,
                 _cycle = nil,    }

 function _M.Init(self, cycle)
     if not self._slots then
         self._slots = {}
         self.] = {}
         self.] = {}
         self.] = {}
         self.] = {}
         utils.tinsert_n(self.], {}, )
         utils.tinsert_n(self.], {}, )
         utils.tinsert_n(self.], {}, )
         utils.tinsert_n(self.], {}, )
     end
     if not self._cycle then
         self._cycle = cycle
     end
 end

 function _M.Update(self, cycle)
     local h1, m1, s1, ms1 = utils.ms2t(self._cycle)
     self._cycle = cycle
     local h2, m2, s2, ms2 = utils.ms2t(self._cycle)
     self:__UpdateT__(, , h1, h2, utils.bind(self.__UpdateH__, self))
     self:__UpdateT__(, , m1, m2, utils.bind(self.__UpdateM__, self))
     self:__UpdateT__(, , s1, s2, utils.bind(self.__UpdateS__, self))
     self:__UpdateT__(, , ms1, ms2, utils.bind(self.__UpdateMS__, self))
 end

 function _M.AddTimer(self, delay, func)
     self:__Insert__(delay + , func)
 end

 function _M.__Insert__(self, delay, func)
      == delay then
         func()
     else
         local h1, m1, s1, ms1 = utils.ms2t(delay)
         local h2, m2, s2, ms2 = utils.ms2t(delay + self._cycle)
         local tick = {    func = func,
                         time = { h = h2, m = m2, s = s2, ms = ms2 } }
          then
             tinsert(self.][h2 ==   or h2], tick)
          then
             tinsert(self.][m2 ==   or m2], tick)
          then
             tinsert(self.][s2 ==   or s2], tick)
          then
             tinsert(self.][ms2 ==   or ms2], tick)
         end
     end
 end

 function _M.__UpdateT__(self, cycle, index, first, last, func)
     local slots = self._slots[index]
     while first ~= last do
         first = first +
         , #slots[first] do
             func(slots[first][i])
         end
         slots[first] = {}
         first = first % cycle
     end
 end

 function _M.__UpdateH__(self, v)
     self:__Insert__(utils.t2ms(, v.time.m, v.time.s, v.time.ms), v.func)
 end

 function _M.__UpdateM__(self, v)
     self:__Insert__(utils.t2ms(, , v.time.s, v.time.ms), v.func)
 end

 function _M.__UpdateS__(self, v)
     self:__Insert__(utils.t2ms(, , , v.time.ms), v.func)
 end

 function _M.__UpdateMS__(self, v)
     self:__Insert__(utils.t2ms(, , , ), v.func)
 end

 return _M

源码下载

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

  1. 经典多级时间轮定时器(C语言版)

    经典多级时间轮定时器(C语言版) 文章目录 经典多级时间轮定时器(C语言版) 1. 序言 2. 多级时间轮实现框架 2.1 多级时间轮对象 2.2 时间轮对象 2.3 定时任务对象 2.4 双向链表 ...

  2. go:基于时间轮定时器方案

    /* * http://blog.csdn.net/yueguanghaidao/article/details/46290539 * 修改内容:为定时器增加类型和参数属性,修改回调函数类型 */ p ...

  3. Kafka中时间轮分析与Java实现

    在Kafka中应用了大量的延迟操作但在Kafka中 并没用使用JDK自带的Timer或是DelayQueue用于延迟操作,而是使用自己开发的DelayedOperationPurgatory组件用于管 ...

  4. [从源码学设计]蚂蚁金服SOFARegistry之时间轮的使用

    [从源码学设计]蚂蚁金服SOFARegistry之时间轮的使用 目录 [从源码学设计]蚂蚁金服SOFARegistry之时间轮的使用 0x00 摘要 0x01 业务领域 1.1 应用场景 0x02 定 ...

  5. kafka时间轮的原理(一)

    概述 早就想写关于kafka时间轮的随笔了,奈何时间不够,技术感觉理解不到位,现在把我之前学习到的进行整理一下,以便于以后并不会忘却.kafka时间轮是一个时间延时调度的工具,学习它可以掌握更加灵活先 ...

  6. .Net 之时间轮算法(终极版)

    关于时间轮算法的起始 我也认真的看了时间轮算法相关,大致都是如下的一个图 个人认为的问题 大部分文章在解释这个为何用时间轮的时候都再说 假设我们现在有一个很大的数组,专门用于存放延时任务.它的精度达到 ...

  7. 时间轮算法的定时器(Delphi)

    源码下载 http://files.cnblogs.com/lwm8246/uTimeWheel.rar D7,XE2 编译测试OK //时间轮算法的定时器 //-- : QQ unit uTimeW ...

  8. Kafka解惑之时间轮 (TimingWheel)

    Kafka中存在大量的延迟操作,比如延迟生产.延迟拉取以及延迟删除等.Kafka并没有使用JDK自带的Timer或者DelayQueue来实现延迟的功能,而是基于时间轮自定义了一个用于实现延迟功能的定 ...

  9. 时间轮算法(TimingWheel)是如何实现的?

    前言 我在2. SOFAJRaft源码分析-JRaft的定时任务调度器是怎么做的?这篇文章里已经讲解过时间轮算法在JRaft中是怎么应用的,但是我感觉我并没有讲解清楚这个东西,导致看了这篇文章依然和没 ...

随机推荐

  1. 修复关于apache-xampp的问题:Port 443 in use by “vmware-hostd.exe”!

    内容提要:复关于apache-xampp的问题:Port 443 in use by “vmware-hostd.exe”!在电脑里装了VMware后,再要装xampp,十有八九就会出现这个问题: 复 ...

  2. 使用Python对Excel进行读写操作

    学习Python的过程中,我们会遇到Excel的读写问题.这时,我们可以使用xlwt模块将数据写入Excel表格中,使用xlrd模块从Excel中读取数据.下面我们介绍如何实现使用Python对Exc ...

  3. Visual Studio 2015 Professional 破解

    Visual Studio 2015 Professional 版本 破解序列号:HMGNV-WCYXV-X7G9W-YCX63-B98R2

  4. Java基础学习(五)—Collection

    一.Collection概述 1.数组和集合的区别 (1)长度区别      数组长度是固定的,集合长度是可变的. (2)存储内容区别      数组只能存放同一种类型的元素(基本类型/引用类型). ...

  5. 【WCF】错误处理(四):一刀切——IErrorHandler

    前面几篇烂文中所介绍到的错误方式,都是在操作协定的实现代码中抛出 FaultException 或者带泛型参数的detail方案,有些时候,错误的处理方法比较相似,可是要每个操作协定去处理,似乎也太麻 ...

  6. [认证授权] 3.基于OAuth2的认证(译)

    OAuth 2.0 规范定义了一个授权(delegation)协议,对于使用Web的应用程序和API在网络上传递授权决策非常有用.OAuth被用在各钟各样的应用程序中,包括提供用户认证的机制.这导致许 ...

  7. Nginx VS Apache

    作为 Web 服务器:相比 Apache,Nginx 使用更少的资源,支持更多的并发连接,体现更高的效率 Nginx 静态处理性能比 Apache 高 3倍以上 最核心的区别在于apache是同步多进 ...

  8. 2017-2-17,c#基础,输入输出,定义变量,变量赋值,int.Parse的基础理解,在本的初学者也能看懂(未完待续)

    计算机是死板的固定的,人是活跃的开放的,初学c#第一天给我的感觉就是:用人活跃开放式的思维去与呆萌的计算机沟通,摸清脾气,有利于双方深入合作,这也是今晚的教训,细心,仔细,大胆 c#基础 1.Hell ...

  9. POJ 3294 出现在至少K个字符串中的子串

    在掌握POJ 2774(两个串求最长公共子串)以及对Height数组分组后,本题还是容易想出思路的. 首先用字符集外的不同字符连接所有串,这是为了防止两个后缀在比较时超过某个字符串的分界.二分子串的长 ...

  10. 2017年4月 TIOBE 编程语言排名

    2017年4月 TIOBE 编程语言排名 Hack是Facebook 在三年推出的PHP方言,在2017年4月首次进入TIOBE编程语言排行榜前50位. Hack原是Facebook的内部项目,与20 ...