时间轮

老早之前就听说时间轮算法特别高效,Linux内核都用的它,这两天抽空实现了遍……嗯,被差一bug搞死(~ ̄▽ ̄~) 啊哈

网上扣来的图,原理好懂:轮子里的每格代表一小段时间(精度),连起来就能表示时间点了(我去年买了个表),格子内含链表,中存回调函数;时间指针每次转动一格,指向某格时,取出链表里的回调函数依次执行,后清空链表,等待下一次转动。

加入节点逻辑也简单:在轮子可表示的时间范围内(格子数*格子精度),配合当前时针位置,对格子总数取余,即得本节点需放哪个格子。

进一步为扩大时间轮的表示范围,使用分级方式,跟时分秒一样,上一级转一圈,下一级动一格。

 

对吧,很容易理解……然后coding是另一码事(╯‵□′)╯︵┴─┴

首先,最重要的,数据结构:用了环形链表,方便增删。

struct NodeLink {NodeLink* prev;NodeLink* next;NodeLink() { prev = next = this; } //circle
};
struct stWheel {NodeLink* slots; //每个slot维护的node链表为一个环,slot->next为node链表中第一个节点,prev为node的最后一个节点const uint32 size;uint32 slotIdx;stWheel(uint32 n) : size(n), slotIdx(0){ slots = new NodeLink[size]; }~stWheel() {if (slots) {for (uint32 j = 0; j < size; ++j) {NodeLink* link = (slots + j)->next;while (link != slots + j) {TimerNode* node = (TimerNode*)link;link = node->link.next;delete node;}}delete[]slots;}}
};

具体时间节点的数据结构如下:

struct TimerNode {Pool_Obj_Define(TimerNode, 32) //内存池声明,不含数据NodeLink link; //must in the head
    uint32 timeDead;uint32 interval; //间隔多久int loop;        //总共循环多久std::function<void()> func;
};

TimeNode里保存上下关系,stWheel的NodeLink辅助用的,环状链表的头,没实际数据,用以记录首尾TimeNode。

核心代码如下:

——增删节点——

void CTimerMgr::_AddTimerNode(uint32 milseconds, TimerNode* node) {NodeLink* slot = NULL;uint32 tickCnt = milseconds / TIME_TICK_LEN;if (tickCnt < WHEEL_CAP[0]) {uint32 index = (_wheels[0]->slotIdx + tickCnt) & (WHEEL_SIZE[0] - 1); //2的N次幂位操作取余slot = _wheels[0]->slots + index;} else {for (int i = 1; i < WHEEL_NUM; ++i) {if (tickCnt < WHEEL_CAP[i]) {uint32 preCap = WHEEL_CAP[i - 1]; //上一级总容量即为本级的一格容量uint32 index = (_wheels[i]->slotIdx + tickCnt / preCap - 1) & (WHEEL_SIZE[i] - 1); //勿忘-1slot = _wheels[i]->slots + index;break;}}}NodeLink* link = &(node->link);link->prev = slot->prev; //插入格子的prev位置(尾节点)link->prev->next = link;link->next = slot;slot->prev = link;
}
void CTimerMgr::RemoveTimer(TimerNode* node) {LOG_TRACK("node[%p], timeDead[%lld]", node, node->timeDead);NodeLink* link = &(node->link);if (link->prev) {link->prev->next = link->next;}if (link->next) {link->next->prev = link->prev;}link->prev = link->next = NULL;delete node;
}

——轮子启动——

void CTimerMgr::CheckTimerList(const uint32 timenow) {uint32 tickCnt = timenow > _checkTime ? (timenow - _checkTime) / TIME_TICK_LEN : 0;//if (tickCnt) Printf();for (uint32 i = 0; i < tickCnt; ++i) { //扫过的slot均超时stWheel* wheel = _wheels[0];NodeLink* slot = wheel->slots + wheel->slotIdx;NodeLink* link = slot->next;slot->next = slot->prev = slot; //清空当前格子while (link != slot) {            //环形链表遍历TimerNode* node = (TimerNode*)link;link = node->link.next; //得放在前面,后续函数调用,可能会更改node的链接关系
            AddToReadyNode(node);}if (++(wheel->slotIdx) >= wheel->size) {wheel->slotIdx = 0;Cascade(1, timenow); //跳级
        }_checkTime += TIME_TICK_LEN;}DoTimeOutCallBack();
}
uint32 CTimerMgr::Cascade(uint32 wheelIdx, const uint32 timenow) {if (wheelIdx < 1 || wheelIdx >= WHEEL_NUM) {return 0;}int casCnt = 0;stWheel* wheel = _wheels[wheelIdx];NodeLink* slot = wheel->slots + wheel->slotIdx;NodeLink* link = slot->next;slot->next = slot->prev = slot; //清空当前格子while (link != slot) {TimerNode* node = (TimerNode*)link;link = node->link.next;if (node->timeDead <= timenow) {AddToReadyNode(node);} else {_AddTimerNode(node->timeDead - timenow, node); //本级精度下已超时,精度提升,重新加一遍++casCnt;LOG_TRACK("wheelIdx[%u], link[%p], milseconds[%u]", wheelIdx, link, node->timeDead - timenow);}}if (++(wheel->slotIdx) >= wheel->size) {wheel->slotIdx = 0;casCnt += Cascade(++wheelIdx, timenow);}return casCnt;
}

 

那么问题来了:大于,大于等于,边界,减一……搞错几多次 ○(* ̄︶ ̄*)○ 吃饱睡好

 

 

源码地址:https://github.com/3workman/Tools/tree/master/src/Timer

转载于:https://www.cnblogs.com/3workman/p/6063819.html

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

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

相关文章

qc35 说明书_使用Bose QC35 2年的心得 | 迟而不迟的深度体验 | 文附佩戴效果照片...

小编注&#xff1a;此篇文章来自即可瓜分10万金币&#xff0c;周边好礼达标就有&#xff0c;邀新任务奖励无上限&#xff0c;点击查看活动详情创作立场声明&#xff1a;本文所测商品为自费购入&#xff0c;我会在文中点明。坚持来自内心的主观评测是起码的底线&#xff0c;不会…

threadgroup_Java ThreadGroup类的checkAccess()方法和示例

threadgroupThreadGroup类的checkAccess()方法 (ThreadGroup class checkAccess() method) checkAccess() method is available in java.lang package. checkAccess()方法在java.lang包中可用。 checkAccess() method is used to check whether the currently running thread h…

qt tab弹出特效_Nuke Studio 12(影视特效合成软件)中文版分享

Nuke 12是一款功能强大&#xff0c;世界知名的影视后期特效合成软件。NUKE是一个获得学院奖(Academy Award)的数码合成软件。已经经过10年的历练&#xff0c;为艺术家们提供了创造具有高质素的相片效果的图像的方法。NUKE无需专门的硬件平台&#xff0c;但却能为艺术家提供组合…

c ++ 链表_C ++程序查找两个单个链表的并集

c 链表Problem statement: Write a C program to find the union of two single linked lists. 问题陈述&#xff1a;编写一个C 程序来查找两个单个链表的并集。 Example: 例&#xff1a; Let the first linked list be:5->4->3->6->1->NULLLet the second l…

精华版线段树模板

哈哈哈&#xff0c;打了一上午。。。#include<iostream> #include<cstdio> #include<cstdlib> #include<cstring> #include<cmath> #include<algorithm> using namespace std; typedef long long ll; ll a[10000010]; ll lazy[1000000]; …

【转】unity地形插件T4M使用帮助

unity的地形系统在手机游戏中因为效率问题基本无法使用&#xff0c;只能通过T4M这个地形插件来进行优化制作。下面大概讲解一下使用流程及方法。 先中U3D里面用自带的地形系统刷出想要的地形和贴图。贴图可以大概刷一下。后面要重新刷。 用导出脚本ExportTerrain.js导出地形为O…

ansys添加力矩_ANSYS软件中施加扭矩的方法

ANSYS软件中施加扭矩的方法胡意立&#xff0c;孙明礼&#xff0c;沈燕青&#xff0c;周佳杰&#xff0c;胡林强【摘要】在机械结构的有限元分析中&#xff0c;常会遇到施加扭矩的问题。文中探讨了在ANSYS软件中施加扭矩的一种方法&#xff0c;以在一个六棱柱一端施加扭矩为实例…

Python | 程序从列表中删除重复的元素

Example: 例&#xff1a; Input:list1: [10, 20, 10, 20, 30, 40, 30, 50]Output:List after removing duplicate elementslist2: [10, 20, 30, 40, 50]Logic: 逻辑&#xff1a; To implement the program is too easy, we have to append elements one by one to another…

Linux的简介与虚拟机的管理

Linux的简介&#xff1a; 严格的来讲&#xff0c;Linux不算是一个操作系统&#xff0c;只是一个Linux系统中的内核&#xff0c;Linux的全称是GUN/Linux&#xff0c;这才算是一个真正意义上的Linux系统。 Linux是一个多用户多任务的操作系统&#xff0c;拥有良好的用户界面&…

python递归查找_Python程序使用递归查找数字的幂

python递归查找Given the base x and the power y and we have to find the x to the power y using recursion in Python. 给定基数x和幂y &#xff0c;我们必须使用Python中的递归找到x到幂y 。 By using recursion – We will be multiplying a number (initially with val…

phalapi可以依赖注入么_PHP 依赖注入

通常调用一个类里面的方法需要如何操作&#xff1a;$class new class();$class->fun()依赖注入模式用来减少程序间的耦合依赖注入共有三种模式&#xff1a;setter 方法注入着重说下setter方法注入并结合ArrayAccess/*** Class Di* property People*/class Di implements Ar…

R语言:ggplot2精细化绘图——以实用商业化图表绘图为例(转)

本文旨在介绍R语言中ggplot2包的一些精细化操作&#xff0c;主要适用于对R画图有一定了解&#xff0c;需要更精细化作图的人&#xff0c;尤其是那些刚从excel转ggplot2的各位&#xff0c;有比较频繁的作图需求的人。不讨论那些样式非常酷炫的图表&#xff0c;以实用的商业化图表…

Linux中常用的命令

1.文件建立 touch file&#xff08;文件的名字&#xff09; 注意&#xff1a; touch不但可以建立文件也可以修改文件的时间戳 时间戳分为&#xff1a; atime&#xff1a;文件内容被访问的时间标识 mtime&#xff1a;文件内容被修改的时间标识 ctime&#xff1a;文件属性或文件内…

蓝桥杯宝藏排序题目算法(冒泡、选择、插入)

冒泡排序: def bubble_sort(li): # 函数方式for i in range(len(li)-1):exchangeFalsefor j in range(len(li)-i-1):if li[j]>li[j1]:li[j],li[j1]li[j1],li[j]exchangeTrueif not exchange:return 选择排序: 从左往右找到最小的元素&#xff0c;放在起始位置…

hive分区用2个字段有何限制_[特性]Hive动态分区功能使用

[特性]Hive动态分区功能使用2016-01-31 21:40说明Hive有两种分区&#xff0c;一种是静态分区&#xff0c;也就是普通的分区。另一种是动态分区。动态分区在数据导入时&#xff0c;会根据具体的字段值自行决定导入&#xff0c;并创建相应的分区。使用上更为方面。举例准备工作创…

Linux系统中输出输入的管理

1.什么是输入和输出 输入和输出是计算机系统中的主机与外部进行通信的系统。它由外围设备和输入输出控制系统两部分组成&#xff0c;我们在shell中键入指令&#xff0c;然后送入CPU中运算产生结果&#xff0c;再将结果送到字符设备中显示。简单点来说输入输出就是通过我们的键盘…

find 命令示例_数组find()方法以及JavaScript中的示例

find 命令示例JavaScript find()方法 (JavaScript find() method) find() method is used to get the first element from an array which passes the given test (condition). find()方法用于从通过给定测试(条件)的数组中获取第一个元素。 Syntax: 句法&#xff1a; array.…

统计Apache或Nginx访问日志里的独立IP访问数量的Shell

1、把IP数量直接输出显示&#xff1a; cat access_log_2011_06_26.log |awk ‘{print $1}’|uniq -c|wc -l 2、把IP数量输出到文本显示&#xff1a; cat access_log_2011_06_26.log |awk ‘{print $1}’|uniq -c|wc -l > ip.txt 总结&#xff1a;如果单个访问日志大小超过2G…

ggplot2箱式图两两比较_R绘图 第四篇:绘制箱图(ggplot2)

箱线图通过绘制观测数据的五数总括&#xff0c;即最小值、下四分位数、中位数、上四分位数以及最大值&#xff0c;描述了变量值的分布情况。箱线图能够显示出离群点(outlier)&#xff0c;离群点也叫做异常值&#xff0c;通过箱线图能够很容易识别出数据中的异常值。箱线图提供了…

Linux系统中用户的管理

#####用户管理###### 1在Linux中&#xff0c;有三种用户&#xff1a; 1 root : 也成为超级用户&#xff0c;对系统有控制权限&#xff0c;超级用户可以不受限制的运行任何命令&#xff0c;root 用户可以看作是系统的管理员。 2 系统用户&#xff1a; 系统用户通常为系统功能所必…