打印当前时间 毫秒_时间轮定时器
时间轮算法(Timing-Wheel)很早出现在linux kernel 2.6中。因效率非常高,很多应用框架都实现了这个算法。还有些定时器使用最小堆实现,但总体来说,时间轮算法在插入性能上更高。
前面分析libco的时候,也讲到其实现了一个时间轮定时器,不过这个定时器只有一个轮,且长度是60000,仅仅实现了60秒的定时器范围,这个不免有些残缺。
这一篇想介绍一个完整的定时器实现,使用5个轮子,一共512个结点,如果精度是1毫秒,可以定时长达2^32-1毫秒这么长,粗略算起来接近50天。
时间轮分成多个层级,每一层是一个圈,和时钟类似,和水表更像,如下面图:
![](/assets/blank.gif)
当个位的指针转完一圈到达0这个刻度之后,十位的指针转1格;当十位的转完一圈,百位的转1格,以此类推。这样就能表示很长的度数。
时间轮能表达的时间长度,和圈的数量以及每一圈的长度成正比。假设有5圈,每个圈60个刻度,每个刻度表示1毫秒,那么这个时间轮可以表示这么长:
60 x 60 x 60 x 60 x 60 = 777,600,000(ms) ~ 216小时
现在用程序表示这个指针可能这样的:
int ptr[5];
假如有一个update函数驱动时间轮运转起来,每调一次跳一格,那这个算法可能是这样的:
- ptr[0]加1,如果ptr[0]等于60,则使ptr[0]重置为0,可以写为:
ptr[0] = (ptr[0] + 1) % 60
- 如果ptr[0]等于0的时候,说明转了一圈,此时ptr[1]要加1:
ptr[1] = (ptr[1] + 1) % 60
- 就这样一直转到第5圈,然后又重新开始。
这样处理有点麻烦,而且还需要一个数组来表示。其实可以用一个uint32变量来划分,比如:
| 6bit | 6bit | 6bit | 6bit | 8bit |111111 111111 111111 111111 11111111
也分5个圈,第1个占8位即256个槽位,后面4个分别占6位即64个槽位。这个变量只需不断自增就行,比如前面8位满了会变成0,后面自动进1。这样就可以定义一些宏:
// 第1个轮占的位数
#define TVR_BITS 8
// 第1个轮的长度
#define TVR_SIZE (1 << TVR_BITS)
// 第n个轮占的位数
#define TVN_BITS 6
// 第n个轮的长度
#define TVN_SIZE (1 << TVN_BITS)
// 掩码:取模或整除用
#define TVR_MASK (TVR_SIZE - 1)
#define TVN_MASK (TVN_SIZE - 1)
想取某一个圈的当前指针位置是:
// 第1个圈的当前指针位置
#define FIRST_INDEX(v) ((v) & TVR_MASK)
// 后面第n个圈的当前指针位置
#define NTH_INDEX(v, n) (((v) >> (TVR_BITS + (n) * TVN_BITS)) & TVN_MASK)
核心的数组结构是这样的:
// 第1个轮
typedef struct tvroot {clinknode_t vec[TVR_SIZE];
} tvroot_t;
// 后面几个轮
typedef struct tvnum {clinknode_t vec[TVN_SIZE];
} tvnum_t;// 时间轮定时器
typedef struct timerwheel {tvroot_t tvroot; // 第1个轮tvnum_t tv[4]; // 后面4个轮uint64_t lasttime; // 上一次的时间毫秒uint32_t currtick; // 当前的tickuint16_t interval; // 每个时间点的毫秒间隔uint16_t remainder; // 剩余的毫秒
} timerwheel_t;
这里要重点讲一下clinknode_t
,这是每一个圈的槽位的数据,它其实是一个双向循环链表:
![](/assets/blank.gif)
新结点可以往head的前面加,也可以往head的后面加,相当于加到链表头和链表尾。初始情况下head的前后指针指向自己。链表中的结点就是定时器结点。
双向链表的代码如下:
#pragma once
/*** 循环双向链表*/// 链表结点
typedef struct clinknode {struct clinknode *next;struct clinknode *prev;
} clinknode_t;// 初始化链表头:前后都指向自己
static inline void clinklist_init(clinknode_t *head) {head->prev = head;head->next = head;
}// 插入结点到链表的前面,因为是循环链表,其实是在head的后面
static inline void clinklist_add_front(clinknode_t *head, clinknode_t *node) {node->prev = head;node->next = head->next;head->next->prev = node;head->next = node;
}// 插入结点到链表的后面,因为是循环链表,所以其实是在head的前面
static inline void clinklist_add_back(clinknode_t *head, clinknode_t *node) {node->prev = head->prev;node->next = head;node->prev->next = node;head->prev = node;
}// 判断链表是否为空:循环链表为空是头的下一个和上一个都指向自己
static inline int clinklist_is_empty(clinknode_t *head) {return head == head->next;
}// 从链表中移除自己,同时会重设结点
static inline void clinklist_remote(clinknode_t *node) {node->next->prev = node->prev;node->prev->next = node->next;clinklist_init(node);
}// 将链表1的结点取出来,放到链表2
static inline void clinklist_splice(clinknode_t *head1, clinknode_t *head2) {if (!clinklist_is_empty(head1)) {clinknode_t *first = head1->next; // 第1个结点clinknode_t *last = head1->prev; // 最后1个结点clinknode_t *at = head2->next; // 插在第2个链表的这个结点前面first->prev = head2;head2->next = first;last->next = at;at->prev = last;clinklist_init(head1);}
}
整个定时器的代码如下:
timerwheel.h
#pragma once
/*** 定时器模块*/
#include <stdio.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include "clinklist.h"// 时间轮定时器// 第1个轮占的位数
#define TVR_BITS 8
// 第1个轮的长度
#define TVR_SIZE (1 << TVR_BITS)
// 第n个轮占的位数
#define TVN_BITS 6
// 第n个轮的长度
#define TVN_SIZE (1 << TVN_BITS)
// 掩码:取模或整除用
#define TVR_MASK (TVR_SIZE - 1)
#define TVN_MASK (TVN_SIZE - 1)// 定时器回调函数
typedef void (*timer_cb_t)(void*);// 定时器结点
typedef struct timernode {struct linknode *next; // 下一个结点struct linknode *prev; // 上一个结点void *userdata; // 用户数据timer_cb_t callback; // 回调函数uint32_t expire; // 到期时间
} timernode_t;// 第1个轮
typedef struct tvroot {clinknode_t vec[TVR_SIZE];
} tvroot_t;// 后面几个轮
typedef struct tvnum {clinknode_t vec[TVN_SIZE];
} tvnum_t;// 时间轮定时器
typedef struct timerwheel {tvroot_t tvroot; // 第1个轮tvnum_t tv[4]; // 后面4个轮uint64_t lasttime; // 上一次的时间毫秒uint32_t currtick; // 当前的tickuint16_t interval; // 每个时间点的毫秒间隔uint16_t remainder; // 剩余的毫秒
} timerwheel_t;// 初始化时间轮,interval为每帧的间隔,currtime为当前时间
void timerwheel_init(timerwheel_t *tw, uint16_t interval, uint64_t currtime);
// 初始化时间结点:cb为回调,ud为用户数据
void timerwheel_node_init(timernode_t *node, timer_cb_t cb, void *ud);
// 增加时间结点,ticks为触发间隔(注意是以interval为单位)
void timerwheel_add(timerwheel_t *tw, timernode_t *node, uint32_t ticks);
// 删除结点
int timerwheel_del(timerwheel_t *tw, timernode_t *node);
// 更新时间轮
void timerwheel_update(timerwheel_t *tw, uint64_t currtime);
timerwheel.c
#include "timerwheel.h"#define FIRST_INDEX(v) ((v) & TVR_MASK)
#define NTH_INDEX(v, n) (((v) >> (TVR_BITS + (n) * TVN_BITS)) & TVN_MASK)void timerwheel_init(timerwheel_t *tw, uint16_t interval, uint64_t currtime) {memset(tw, 0, sizeof(*tw));tw->interval = interval;tw->lasttime = currtime;int i, j;for (i = 0; i < TVR_SIZE; ++i) {clinklist_init(tw->tvroot.vec + i);}for (i = 0; i < 4; ++i) {for (j = 0; j < TVN_SIZE; ++j)clinklist_init(tw->tv[i].vec + j);}
}void timerwheel_node_init(timernode_t *node, timer_cb_t cb, void *ud) {node->next = 0;node->prev = 0;node->userdata = ud;node->callback = cb;node->expire = 0;
}static void _timerwheel_add(timerwheel_t *tw, timernode_t *node) {uint32_t expire = node->expire;uint32_t idx = expire - tw->currtick;clinknode_t *head;if (idx < TVR_SIZE) {head = tw->tvroot.vec + FIRST_INDEX(expire);} else {int i;uint64_t sz;for (i = 0; i < 4; ++i) {sz = (1ULL << (TVR_BITS + (i+1) * TVN_BITS));if (idx < sz) {idx = NTH_INDEX(expire, i);head = tw->tv[i].vec + idx;break;}}}clinklist_add_back(head, (clinknode_t*)node);
}void timerwheel_add(timerwheel_t *tw, timernode_t *node, uint32_t ticks) {node->expire = tw->currtick + ((ticks > 0) ? ticks : 1);_timerwheel_add(tw, node);
}int timerwheel_del(timerwheel_t *tw, timernode_t *node) {if (!clinklist_is_empty((clinknode_t*)node)) {clinklist_remote((clinknode_t*)node);return 1;}return 0;
}void _timerwheel_cascade(timerwheel_t *tw, tvnum_t *tv, int idx) {clinknode_t head;clinklist_init(&head);clinklist_splice(tv->vec + idx, &head);while (!clinklist_is_empty(&head)) {timernode_t *node = (timernode_t*)head.next;clinklist_remote(head.next);_timerwheel_add(tw, node);}
}void _timerwheel_tick(timerwheel_t *tw) {++tw->currtick;uint32_t currtick = tw->currtick;int index = (currtick & TVR_MASK); if (index == 0) {int i = 0;int idx;do {idx = NTH_INDEX(tw->currtick, i);_timerwheel_cascade(tw, &(tw->tv[i]), idx);} while (idx == 0 && ++i < 4);}clinknode_t head;clinklist_init(&head);clinklist_splice(tw->tvroot.vec + index, &head);while (!clinklist_is_empty(&head)) {timernode_t *node = (timernode_t*)head.next;clinklist_remote(head.next);if (node->callback) {node->callback(node->userdata);}}
}void timerwheel_update(timerwheel_t *tw, uint64_t currtime) {if (currtime > tw->lasttime) {int diff = currtime - tw->lasttime + tw->remainder;int intv = tw->interval;tw->lasttime = currtime;while (diff >= intv) {diff -= intv;_timerwheel_tick(tw);}tw->remainder = diff;}
}
核心函数就两个:
- _timerwheel_add 把定时器结点加到时间轮里。
- _timerwheel_tick 指针往前走一步,如果指针到达0,则上一个圈的指针也会跳一步,此时要把上一个圈的链表取出来,重新加到当前圈里(_timerwheel_cascade)
这份代码参考了2.6内核的timer,其中涉及到一些位运算,不过通过上面的讲解,应该不难理解。有兴趣了解的还是看代码吧,代码才是最好的文档:)
打印当前时间 毫秒_时间轮定时器相关推荐
- c#获取当前时间 毫秒_《Linux设备驱动程序》(十二)——时间操作(一)
之前我们学会了如何编写一个字符设备,并对其中的一些重要操作进行了说明.对于一个完整的设备而已,可能还有许多工作要做. 本节我们将要说一下内核中是如何对时间问题进行操作的. 本节主要涉及到以下内容: 内 ...
- pwm一个时间单位_时间的换算单位是怎么换算的啊,秒,毫秒,微妙,纳秒等
展开全部 常见时间单位换算: 1秒=1000毫秒(ms) 1秒=1,000,000 微秒62616964757a686964616fe58685e5aeb931333366303836(μs) 1秒= ...
- calendar当前时间整点_时间处理相关类
时间处理相关类 在计算机世界,我们把1970 年 1 月 1 日 00:00:00定为基准时间,每个度量单位是毫秒(1秒的千分之一). 时间相关类有如下 Date时间类(java.util.Date) ...
- 数学建模时间序列分析_时间序列分析建模验证
数学建模时间序列分析 时间序列预测 (Time Series Forecasting) 背景 (Background) This article is the fourth in the series ...
- mysql获取时间_时间类型_时间格式化
为了方便在数据库中存储日期和时间,MySQL提供了表示日期和时间的数据类型,分别是YEAR.DATE.TIME.DATETIME和TIMESTAMP.如下表列举了这些MySQL中日期和时间数据类型所对 ...
- 人生时间计算器_时间计算器
匿名用户 1级 2010-07-14 回答 展开全部 将下面代码复制到txt文件中,并将其保存为Form1.frm,然后运行 但是要注意:填入的时间不能隔日,即开始时间与结束时间都必须在同一天. VE ...
- vue怎么截取时间年月_时间格式的转化 vue与js 年月日 时分秒
首先使用原生转化的方法 第一种 //时间转换 dateStr(d, sign) { //如果没有传递符号,给一个默认的符号 if (!sign) { sign = '-' } //获取d里面年月日时分 ...
- java方法里面能改定时器的时间吗_Kafka 时间轮的原理和实现
女主宣言 Kafka 作为一个支持实时处理大量请求的分布式流处理平台,需要一个设计良好的定时器来处理异步任务.本文作者将基于 Kafka 1.1.0 版本的源码来介绍 Kafka 中定时器的基础数据结 ...
- linux编程之经典多级时间轮定时器(C语言版)
一. 多级时间轮实现框架 上图是5个时间轮级联的效果图.中间的大轮是工作轮,只有在它上的任务才会被执行:其他轮上的任务时间到后迁移到下一级轮上,他们最终都会迁移到工作轮上而被调度执行. 多级时间轮的原 ...
最新文章
- 为什么你的工作经验不值钱
- java jni key_JNIKeyProtection
- live555 编译 linux,在树莓派上搭建LIVE555 Streaming Media服务器端
- nth-child(n)和nth-of-type(n)
- 职业生涯起步不要去顶级公司
- C#枚举类型的常用操作总结
- 谷歌暗示android wear未来或兼容ios系统!腾讯,传谷歌今年5月将推出iOS版本Android Wear...
- 谷歌浏览器怎么查找和改变编码格式
- AVOD阅读笔记(二):多模型融合RPN----Aggregate View Obeject Detection network
- PostGIS 报错libcrypto
- Jenkins不能正常trigger
- 使用theano进行深度学习实践(一)
- 操作系统PV大题_小和尚老和尚喝水问题
- 2022年机动车新规,外地人上京牌不需要居住证啦
- matlab实现正弦内插算法(低通滤波)
- java sha256加密_如何用Sha256进行简单的加密或者解密
- json在java代码混淆出问题_代码混淆 GSON完满解决
- html网页鼠标样式、css精灵、iconfont、过渡动画笔记
- 期货十三篇 第七篇 平仓篇
- Vagrant开发环境搭建