【高性能定时器】 时间轮

时间轮


简述

顾名思义,时间轮就像一个轮子,在转动的时候外界会指向轮子不同的区域,该区域就可以被使用。因此只要将不同时间的定时器按照一定的方法散列到时间轮的不同槽(即时间轮划分的区域)之中,就可以实现在运转到某个槽时,进行判断该定时器是否已经到达运行时间(需要判断是由于有的定时器并非在这一圈就需要运行,可能需要后面几圈才会运行。

time-wheel

从图中也可以看出,每个槽中的定时器是以(双向)链表形式存储的,每次添加的时候直接插入到链表的开始(头插法)。值得注意的是,由于使用头插法,因此在运行到某个槽时,需要遍历一遍链表,已检查是否有到达时间的计时器,有的话就运行,并删除结点。

至于在每转到一个槽时都要检查是否到达运行时间,可以这样理解:时间轮进行散列的方法就是取余运算,假设每个槽的间隔为1s,共有n个槽,当前转到了第cur个槽,那么一个定时在 t s以后运行的定时器就要放在第( cur + t % n ) % n个槽,并在运行t / n圈后到达该槽时才会运行。因此一个槽中的定时器运行的时间是相差i(i >= 0)个周期的。

所以时间轮简单来说就是散列 + 链表,这样与使用升序链表相比,散列可以直接定位要插入的槽所在位置,可以提高添加定时器的效率,由O(N)到了O(1)。

对实现时间轮来说,最主要的还是链表的操作是否熟练,当然也主要是双向链表的添加与删除。


代码分析

  • 记录定时器的时间信息,从而获取在时间轮中槽的位置,以及在多少圈之后被触发。在定时时间不足槽之间切换的时间时,要将t/n记为1,否则记录t/n的整除结果。
    // timeout为定时时间,SI为槽之间切换的时间int ticks;if( timeout < SI ) {ticks = 1;} else {ticks = timeout / SI;}int rotation = ticks / N;  // 被触发的圈数int ts = ( cur_slot + ticks % N ) % N;  // 被插入的槽
  • 为提高定时器的添加效率,使用头插法,将定时器添加在槽的开始位置。
  • 使用双向链表,需要注意将后面的结点的pre指向前一个结点。
  • 删除链表时要注意结点是否是第一个结点

代码实现(含注释)

#ifndef _TIMEWHEEL_H_
#define _TIMEWHEEL_H_#include <time.h>
#include <netinet/in.h>const int BUFFER_SIZE = 64;class TwTimer;// 用户数据,绑定socket和定时器
struct client_data {sockaddr_in address;int sock_fd;char buf[BUFFER_SIZE];TwTimer* timer;
};// 定时器类,时间轮采用双向链表
class TwTimer {
public:int rotation;  // 定时器转多少圈后生效int time_slot;  // 记录定时器属于时间轮的哪个时间槽client_data* user_data;  // 客户数据TwTimer* next;  // 指向下一个定时器TwTimer* pre;  // 指向上一个定时器
public:TwTimer( int rot, int ts ) : rotation(rot), time_slot(ts), next(NULL), pre(NULL) {}void (*cb_func)( client_data * );  // 回调函数
};class TimeWheel {
private: static const int N = 60;  // 槽的数目static const int SI = 1;  // 定时器槽之间时间间隔TwTimer* slot[ N ];  // 时间轮的槽,指向一个定时器链表,链表无序int cur_slot;  // 当前槽
public:TimeWheel() : cur_slot(0) {for( int i = 0; i < N; i++ ) {slot[i] = NULL;}}~TimeWheel() {for( int i = 0; i < N; i++ ) {TwTimer* tmp;while( tmp = slot[i], tmp ) {slot[i] = tmp->next;delete tmp;}}}TwTimer* add_timer( int timeout );  // 根据定时值创建定时器,并插入槽中void del_timer( TwTimer* timer );void tick();
};TwTimer* TimeWheel::add_timer( int timeout ) {if( timeout < 0 ) {return NULL;}// 记录多少个tick后被触发,不足最小单位SI的记为1,其余为timeout/SIint ticks = 0;if( timeout < SI ) {ticks = 1;} else {ticks = timeout / SI;}int rotation = ticks / N;  // 被触发的圈数int ts = ( cur_slot + ticks % N ) % N;  // 被插入的槽TwTimer* timer = new TwTimer( rotation, ts );// 如果链表为空,则放到头,否则插入到第一个位置if( !slot[ts] ) {slot[ts] = timer;} else {timer->next = slot[ts];slot[ts]->pre = timer;slot[ts] = timer;}return timer;
}// 删除定时器
void TimeWheel::del_timer( TwTimer* timer ) {if( !timer ) {return;}// 注意链表为双向的int ts = timer->time_slot;if( timer == slot[ts] ) {slot[ts] = slot[ts]->next;if( slot[ts] ) {slot[ts]->pre = NULL;}} else {timer->pre->next = timer->next;if( timer->next ) {timer->next->pre = timer->pre;}}delete timer;
}// SI时间到后,条用该函数,时间轮向前滚动一个槽的间隔
void TimeWheel::tick() {TwTimer* tmp = slot[cur_slot];while( tmp ) {if( tmp->rotation > 0 ) {  // 定时时间未到tmp->rotation--;tmp = tmp->next;} else {  // 定时时间已到tmp->cb_func( tmp->user_data );if( tmp == slot[cur_slot] ) {  // tmp位于链表首slot[cur_slot] = tmp->next;if( slot[cur_slot] ) {slot[cur_slot]->pre = NULL;}delete tmp;tmp = slot[cur_slot];} else {  // tmp位于链表中tmp->pre->next = tmp->next;if( tmp->next ) {tmp->next->pre = tmp->pre;}TwTimer* tmp2 = tmp->next;delete tmp;tmp = tmp2;}}}cur_slot = ( cur_slot + 1 ) % N;
}#endif

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/381871.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

知识管理的三种策略

知识管理的三种策略 面向流程的知识管理战略&#xff0d;实施篇&#xff08;上&#xff09;(AMT研究院 周瑛 编译) 转载于:https://www.cnblogs.com/yangfada/archive/2006/01/24/322381.html

java 考试复习整理——JAVA类和类成员的修饰符

今天整理了一下以前学的JAVA的类和类成员的修饰符&#xff0c;考试的时候可能会考到。一&#xff1a;访问修饰符&#xff1a;1.省略访问修饰符&#xff1a;具有默认的访问特性&#xff0c;即具有包访问特性&#xff0c;只能被同一个包中的类使用。2.public访问修饰符&#xff1…

【安利】程序猿作图神器 - Graphviz

还在为在linux下画二叉树等图苦恼吗&#xff0c;现在就安利一波linux程序猿的作图神器——Graphviz。&#xff08;本来在写其他东西&#xff0c;刚好要绘图&#xff0c;强行插入一篇blog&#xff09; Graphviz (Graph Visualization Software) 是一个由AT&T实验室启动的开源…

仿ISQL功能的实现,可以实现批处理功能

具体请见下载文件&#xff1a;/Files/bigmouthz/DNet写的数据库isql执行程序&#xff08;含源码&#xff09;.rar部分代码如下&#xff1a; DBCore.DataBaseVisitor.AbsDBHelper dbhelper null ; private void bt_DBLink_Click(object sender, System.EventArgs e) { try …

uva 11400——Lighting System Design

题意&#xff1a; 给定一些灯泡&#xff0c;每种灯泡有不同的电压v&#xff0c;电源费用k&#xff0c;每个灯泡的费用c&#xff0c;所需的灯泡的数量L&#xff0c;同种灯泡可以使用相同电源&#xff0c;问最小花费。 思路&#xff1a;dp&#xff0c;每种灯泡要么选要么不选两种…

【高性能定时器】时间堆(最小堆)

最小堆及其应用&#xff1a;时间堆 最小堆及其应用&#xff1a;时间堆 一、 堆1. 概念2. 最小堆的实现3. 性质4. 代码 二、时间堆1. 概念简述2. 实现细节3. 代码 一、 堆 1. 概念 堆是一种经过排序的完全二叉树&#xff0c;其中任一非终端节点的数据值均不大于&#xff08;或…

uva 11584——Partitioning by Palindromes

题意&#xff1a;给定一个字符串&#xff0c;把该字符串划分为最少的回文串。 思路&#xff1a;dp&#xff0c;到达i点的回文串长度都存起来&#xff0c;那么dp[i]min(以i为结尾的最短的回文串长度&#xff09;。 code&#xff1a; #include <bits/stdc.h> using namespa…

【操作系统】生产者消费者问题

生产者消费者模型 文章目录生产者消费者模型 [toc]一、 生产者消费者问题二、 问题分析三、 伪代码实现四、代码实现&#xff08;C&#xff09;五、 互斥锁与条件变量的使用比较一、 生产者消费者问题 生产者消费者问题&#xff08;英语&#xff1a;Producer-consumer problem&…

2006年2月8日 再见,Borland

昨天早上从David I的Blog上看到了Borland出售IDE产品线转而全面发展ALM的消息,既感到惋惜又似乎感到新的希望 之所以惋惜是因为Delphi是我第一个喜欢的语言&#xff0c;也是Delphi第一次让我对编程产生兴趣&#xff0c;还记得我的第一个小软件就是Delphi5.0做的&#xff0c;而如…

【计算机网络】三次握手与四次挥手

三次握手与四次挥手 通过TCP/IP协议的学习&#xff0c;我们可以知道TCP协议是一种面向连接的、可靠的传输协议。其中&#xff0c;为了保证客户端与服务器连接的有效性&#xff0c;就有了本篇文章所要介绍的“三次挥手”&#xff1b;而“四次挥手”则是为了保证连接的正确断开。…

uva 10003——Cutting Sticks

题意&#xff1a;给定一长为L的木棍和n个切割点&#xff0c;每次切割的费用为切割的长度&#xff0c;求最小的费用。 思路&#xff1a;dp,子问题是区间&#xff08;i&#xff0c;j&#xff09;的最小费用&#xff0c;临界是&#xff08;i,j)只有一个切割点。dp[i,j]min(dp[i,k]…

小叔叔又飞走了

小叔叔在飞走之前&#xff0c;昨天晚上&#xff0c;给我们看了&#xff0c;他回老家拍下的&#xff0c;家里每个人的视频&#xff0c;&#xff08;前面这半句话说得好累&#xff0c;不过应该不是病句&#xff09;让我们看了动情又亲切&#xff0c;至少我是这么觉得。一个大家庭…

系统调用中断(EINTR)与SIGCHLD信号的处理

一、被中断的系统调用(EINTR)的理解1. 慢系统调用是&#xff1f;2. 慢系统调用的类别3. EINTR产生的原因5. 一般处理方法 二、SIGCHLD信号的处理1. SIGCHLD信号的产生2. SIGCHLD信号的处理3. 不处理SIGCHLD的后果 三、示例代码 一、被中断的系统调用(EINTR)的理解 1. 慢系…

SMO写的查看数据库信息的代码

要分析一个比较大的数据库&#xff0c;里面的表太多了&#xff0c;虽然是中文命名&#xff0c;但在2005的Management Studio中查看还是比较麻烦&#xff0c;比如&#xff0c;我想查看具有相同字段名称的表的情况就不好办。于是用SMO写了这个东东。代码比较乱&#xff0c;没有进…

uva 1626——Brackets sequence

题意&#xff1a;定义满足 1.空序列 2.&#xff08;&#xff09;&#xff08;X&#xff09;及括号和其括起来的合法序列 3.【】要求和&#xff08;&#xff09;相同 都是合法的串。 然后给定一段序列&#xff0c;求添加最小的&#xff08;&#xff09;或【】使得序列合法。…

定时器与超时的设置

一、相关时间函数1. gettimeofday()2. time()3. clock() 二、间隔定时器1. setitimerval()2. getitimerval()3. 实时定时器的使用 三、为阻塞操作设置超时1. alarm()2. 给read()设置读超时 一、相关时间函数 1. gettimeofday() 获取日历时间。 #include <sys/time.…

世界 Web 2.0 网站评奖揭晓

Web 2.0真的成了我们在互联网不可或缺的一部分&#xff0c;成了互联网的未来趋势.(你现在用了吗?)国外的 SEOmoz 网站最近举行了一个 Over 300 web 2.0 sites reviewed and ranked 活动列出了300多家 Web 2.0 网站, 分为38个分类, 评出了每个类别下的前三名, 并采访了 20位获奖…

win10宽带连接断网自动重连

文章目录1. 断开网络连接&#xff0c;重命名网络连接2. bat代码&#xff1a;检测到断线自动重连3. 设置开机自动执行3.1 方式一&#xff1a;任务计划程序3.2 方式二&#xff1a;用vbs代码开机运行bat1. 断开网络连接&#xff0c;重命名网络连接 中文名字可能出现远程访问错误 …

C#正在被人用来做什么?--在CSDN上引发小讨论的帖子

C#正在被人用来做什么&#xff1f;--在CSDN上引发小讨论的帖子 主  题&#xff1a; C#正在被人用来做什么&#xff1f; 作  者&#xff1a; manio (马牛) 等  级&#xff1a; 信 誉 值&#xff1a; 100 所属社区&#xff1a; .NET技术 C# 问题点数&#xff1a…

uva 1025——A Spy in the Metro

题意&#xff1a;有一个线性的车站&#xff08;1-n&#xff09;&#xff0c;两个方向的车&#xff0c;给出列车的出发时刻和到下一站的时间&#xff0c;要求在到达n前换乘的等待时间最短。 思路&#xff1a;dp&#xff0c;每次有3种决策&#xff0c;要么等一分钟&#xff0c;要…