时间轮
时间轮就像时钟表盘一样,以时钟表盘为例,时钟表盘有秒针分针时针,60秒=1分钟,60分钟=1小时。秒就是最小时间单位。
我们会根据任务想要定时的时间先把任务挂载到秒、分、时的层级上。然后每到下一秒就执行这一秒下面挂载的任务,如果进入下一分钟,就把这一分钟的时间都映射到秒层级。这就是简单的秒分时时间轮。
时间轮有时间指针,每隔最小单位的时间,时间指针就移动一格。
所以时间轮就像循环数组一样,但C语言没有循环数组,所以就使用普通数据+取余运算实现循环数据的效果。
对于同一时间任务采用尾插法将任务连接起来。因为尾插法可以实现先插进来的先执行,按照先进先执行的规则执行。
对于大于60s的任务放在分针层级,大于60分的任务放在小时层级。
当0分钟59秒 到 1分钟时,就会把这一分钟下的任务重新映射到秒针层级。
这个映射是怎么操作的?
比如一个70s的任务在插入时会插入到1分钟对应的链表下,到1分钟后,那这个70s的任务取出来,再进行添加,那岂不是还是原来的位置?难道添加的函数不一样?第一次添加时会进行区分秒分时层级,再取出来重新添加时就调用下一层级的添加函数?
答:插入函数时分级的。
// < 60秒 → 秒轮,60~3600秒 → 分轮,> 3600秒 → 时轮
时间轮结构体有一个参数是uint32_t time; 用来记录当前时间轮的时间,程序运行时为0,每隔1s就加1。
static void
add_node(timer_st *T, timer_node_t *node) {
uint32_t time=node->expire; // 任务到期时间点
uint32_t current_time=T->time; // 当前时间点
uint32_t msec = time - current_time; // 距离任务到期还剩多少时间
// 根据剩余时间进行分级插入
if (msec < ONE_MINUTE) {
link_to(&T->second[time % SECONDS], node);
} else if (msec < ONE_HOUR) {
link_to(&T->minute[(uint32_t)(time/ONE_MINUTE) % MINUTES], node);
} else {
link_to(&T->hour[(uint32_t)(time/ONE_HOUR) % HOURS], node);
}
}
在插入时,会计算
uint32_t msec = time - current_time;
这个msec 参数就是程序运行到这里的时间 距离 任务到期 还有多长时间。
比如在最开始插入70s的定时任务,那么msec =70 - 0 = 70,就会插入到分轮。
当从分轮重新映射到秒轮时,会重新计算msec 。当重新映射时,肯定时到1分钟了,这是msec = 70-60 = 10。这样就映射到10s的位置。
时间轮适用于多线程,通常有一个线程专门负责时间轮的处理,只进行移动时间指针等有关时间轮的操作,但具体任务的执行交给其他专门处理的工作线程。其中时间轮线程会通过消息的形式通知工作线程去执行任务。时间轮只检测不执行。
多线程一般使用时间轮。单线程使用红黑树或者最小堆,跳表使用的较少。而且最小堆分为最小二叉堆 和 最小四叉堆,最小四叉堆相比二叉堆有5%的性能提升。
clock_gettime
clock_gettime是基于Linux C语言的时间函数,可以用于计算精度和纳秒。
//头文件
#include <time.h>
//函数原型
int clock_gettime( clockid_t clock_id,struct timespec * tp );
// timespec 结构体
struct timespec {
__time_t tv_sec; /* 秒 */
__syscall_s long_t tv_nsec; /* 纳秒 */
};
clock_id: 是指使用时钟的类型
CLOCK_REALTIME:是指系统时间,随着系统时间的改变而改变。系统时钟会随用户而改变。系统当前的日历时间。1970年1月1日 00:00:00 UTC 开始计时。
CLOCK_MONOTONIC:指从系统启动时开始计时。不受系统影响,也不会被用户改变。
CLOCK_PROCESS_CPUTIME_ID:指这个进程运行到当前代码时,系统花费的时间。
CLOCK_THREAD_CPUTIME_ID:指这个线程运行到当前代码时,系统花费的时间。
#include<stdio.h>
#include<time.h>
int main(){
struct timespec now;
clock_gettime(CLOCK_MONOTONIC,&now);// 系统开机到运行程序的时间
printf("CLOCK_MONOTONIC: Seconds = %ld \t Nanoseconds = %ld\n", now.tv_sec, now.tv_nsec);
clock_gettime(CLOCK_REALTIME,&now);// 从1970年1月1日 00:00:00计时的时间
printf("CLOCK_REALTIME: Seconds = %ld \t Nanoseconds = %ld\n", now.tv_sec, now.tv_nsec);
clock_gettime(CLOCK_THREAD_CPUTIME_ID,&now);// 线程运行的时间
printf("CLOCK_THREAD_CPUTIME_ID: Seconds = %ld \t Nanoseconds = %ld\n", now.tv_sec, now.tv_nsec);
clock_gettime(CLOCK_PROCESS_CPUTIME_ID,&now);// 进程运行的时间
printf("CLOCK_PROCESS_CPUTIME_ID: Seconds = %ld \t Nanoseconds = %ld\n", now.tv_sec, now.tv_nsec);
return 0;
}
wait-24@DESKTOP-NFQTH04:~/code/time$ ./time
CLOCK_MONOTONIC: Seconds = 641 Nanoseconds = 154814100
CLOCK_REALTIME: Seconds = 1777381108 Nanoseconds = 813390800
CLOCK_THREAD_CPUTIME_ID: Seconds = 0 Nanoseconds = 529700
CLOCK_PROCESS_CPUTIME_ID: Seconds = 0 Nanoseconds = 531300
代码执行
接口
暴露给用户的接口。
void init_timer(void); // 初始化定时器系统
timer_node_t* add_timer(int time, handler_pt func); // 添加定时器,time为相对时间(秒),func为回调函数
void del_timer(timer_node_t *node); // 删除定时器(软删除,标记为已取消)
void check_timer(int *stop); // 主驱动循环,阻塞
void clear_timer(); // 释放所有资源
time_t now_time(); // 获取时钟
用户只需要,初始化,然后添加定时器任务,并在最后记得释放资源即可。
初始化时间轮
创建一个定时器结构体并初始化为空,然后获取一下系统时间作为当前时间。
void
init_timer(void) {
TI = create_timer();
TI->current_point = now_time(); // 获取初始化时的系统时钟时间
}
添加定时器任务
添加定时时间t的任务时,是以程序运行到添加定时器代码时开始计时t,而不是从程序一开始运行就直接开始计时t。
timer_node_t*
add_timer(int time, handler_pt func) {
timer_node_t *node = (timer_node_t *)malloc(sizeof(*node));
spinlock_lock(&TI->lock);
node->expire = time+TI->time; // 计算到时时间
printf("add timer at %u, expire at %u, now_time at %lu, expire_time at %lu\n", TI->time, node->expire, now_time(), node->expire + now_time());
node->callback = func;
node->cancel = 0;
// 若 time <= 0,立即触发,不插入时间轮
if (time <= 0) {
spinlock_unlock(&TI->lock);
node->callback(node);
free(node);
return NULL;
}
add_node(TI, node);
spinlock_unlock(&TI->lock);
return node;
}
怎么准确知道系统到了下一秒?
每0.2s获取一次系统,如果获取的时间与记录的时间不一样,意味着已经度过这一秒,所以就要推进一秒。
// 主驱动循环:每 200ms 轮询一次,追赶流逝的秒数并触发定时器
// 自上轮检查以来每流逝一秒,调用一次 timer_update()
void
check_timer(int *stop) {
while (*stop == 0) {
time_t cp = now_time();
if (cp != TI->current_point) { // 检测到系统时钟前进
uint32_t diff = (uint32_t)(cp - TI->current_point);
TI->current_point = cp;
int i;
for (i=0; i<diff; i++) { // 防止时间跳跃导致定时器丢失
timer_update(TI);
}
}
usleep(200000); // 0.2s
}
}
// 单调时钟(秒)------不受系统时间跳变影响
time_t
now_time() {
struct timespec ti;
clock_gettime(CLOCK_MONOTONIC, &ti); // 获取单调递增的系统时钟(不受NTP调整影响)从开机计算
return ti.tv_sec;// ti.tv_sec:秒 ti.tv_nsec:纳秒
}
删除定时器
采用软删除,将删除标志置一。
会在执行任务的时候,进行检测,如果删除标记置一了,就会释放这个任务。
// 软删除:标记为已取消,节点在其所属槽过期时释放
void
del_timer(timer_node_t *node) {
node->cancel = 1;
}
执行任务
每0.2s检测一下时间,如果进入下一秒就推进时钟,然后使用timer_update函数执行对应槽的任务。
// 主驱动循环:每 200ms 轮询一次,追赶流逝的秒数并触发定时器
// 自上轮检查以来每流逝一秒,调用一次 timer_update()
void
check_timer(int *stop) {
while (*stop == 0) {
time_t cp = now_time();
if (cp != TI->current_point) { // 检测到系统时钟前进
uint32_t diff = (uint32_t)(cp - TI->current_point);
TI->current_point = cp;
int i;
for (i=0; i<diff; i++) { // 防止时间跳跃导致定时器丢失
timer_update(TI);
}
}
usleep(200000); // 0.2s
}
}
释放资源
将所有malloc申请的空间全部释放。
// 释放三轮中所有残留的定时器节点(退出前清理)
void
clear_timer() {
int i;
for (i=0; i<SECONDS; i++) {
link_list_t * list = &TI->second[i];
timer_node_t* current = list->head.next;
while(current) {
timer_node_t * temp = current;
current = current->next;
free(temp);
}
link_clear(&TI->second[i]);
}
for (i=0; i<MINUTES; i++) {
link_list_t * list = &TI->minute[i];
timer_node_t* current = list->head.next;
while(current) {
timer_node_t * temp = current;
current = current->next;
free(temp);
}
link_clear(&TI->minute[i]);
}
for (i=0; i<HOURS; i++) {
link_list_t * list = &TI->hour[i];
timer_node_t* current = list->head.next;
while(current) {
timer_node_t * temp = current;
current = current->next;
free(temp);
}
link_clear(&TI->hour[i]);
}
}
内部实现
结构体
一个单个任务结构体,一个时间轮结构体。
单个任务结构体
struct timer_node {
struct timer_node *next; // 链表指针
uint32_t expire; // 到期时间(秒)
handler_pt callback; // 回调函数
uint8_t cancel; // 删除标记 0:未取消 1:已取消
};
时间轮结构体
// 3层时间轮:随时间推进,定时器从时轮 → 分轮 → 秒轮逐级下放
typedef struct timer {
link_list_t second[SECONDS]; // 秒轮(每槽 1 秒)
link_list_t minute[MINUTES]; // 分轮(每槽 1 分)
link_list_t hour[HOURS]; // 时轮(每槽 1 小时)
spinlock_t lock; // 用于多线程安全操作时间轮
uint32_t time; // 当前时间轮时间(秒,范围 0 ~ HALF_DAY-1)程序启动时为0,每过一秒+1
time_t current_point; // 上次观察到的单调时钟秒数
}timer_st;
// 单向链表,带头哨兵节点和尾指针,支持 O(1) 追加
typedef struct link_list {
timer_node_t head; // 哨兵节点(非真实定时器)
timer_node_t *tail;
}link_list_t;
尾插法
插入结点使用尾插法。
// O(1) 追加到链表尾部,尾插法
static void
link_to(link_list_t *list, timer_node_t *node) {
list->tail->next = node;
list->tail = node;
node->next=0;
}
分级添加结点
添加结点,分级添加
< 60秒 → 秒轮,60~3600秒 → 分轮,> 3600秒 → 时轮
// 根据剩余时间将定时器分级插入对应的轮层:
// < 60秒 → 秒轮,60~3600秒 → 分轮,> 3600秒 → 时轮
static void
add_node(timer_st *T, timer_node_t *node) {
uint32_t time=node->expire; // 任务到期时间点
uint32_t current_time=T->time; // 当前时间点
uint32_t msec = time - current_time; // 距离任务到期还剩多少时间
// 根据剩余时间进行分级插入
if (msec < ONE_MINUTE) {
link_to(&T->second[time % SECONDS], node);
} else if (msec < ONE_HOUR) {
link_to(&T->minute[(uint32_t)(time/ONE_MINUTE) % MINUTES], node);
} else {
link_to(&T->hour[(uint32_t)(time/ONE_HOUR) % HOURS], node);
}
}
剩余时间 msec = expire - current_time
├─ msec < 60秒 → 插入 second[expire % 60]
├─ msec < 3600秒 → 插入 minute[(expire/60) % 60]
└─ msec >= 3600秒 → 插入 hour[(expire/3600) % 12]
根据定时时间的不同插入到不同级别的时间轮。
时钟推进与重新映射
时钟以每秒推进一次,对于此定时器秒就是最小基本单位。
时钟前进,到整分或者整时,就会重新映射到下一级时间轮。
重新映射,即取出任务,并重新添加。
在插入时,会计算
uint32_t msec = time - current_time;
这个msec 参数就是程序运行到这里的时间 距离 任务到期 还有多长时间。
比如在最开始插入70s的定时任务,那么msec =70 - 0 = 70,就会插入到分轮。
当从分轮重新映射到秒轮时,会重新计算msec 。当重新映射时,肯定时到1分钟了,这是msec = 70-60 = 10。这样就映射到10s的位置。
当3540s时 分轮idx = (3540/60)%60 = 59,重新映射分轮第59槽的定时器。
当3600s时,分轮idx = (3600/60)%60 =0,分轮不会出发映射。时轮idx=(3600/3600)%12=1,重新映射时轮第1槽的定时器。
当3660s时,分轮idx = (3660/60)%60 =1,重新映射分轮第1槽的定时器。直接return,时轮不会触发。
在插入的时候3600也是存放在时轮第1槽。时轮第0槽存放的是什么?分轮第0槽存放的是什么?
0-59s在秒轮,
60-119在分轮第1槽,
120-179在分轮第2槽,
......
3540-3599在分轮第59槽。
3600-7199在时轮第1槽,
7200-10799在时轮第2槽,
......
3600*11-3600*12-1在时轮第11槽。
分轮第0槽永远不会有定时器。
时轮第0槽会在12小时回绕时,放入定时器。
// 将高层槽level第idx槽中的定时器逐级下放到低层轮(时→分→秒)
static void
remap(timer_st *T, link_list_t *level, int idx) {
timer_node_t *current = link_clear(&level[idx]);
while (current) {
timer_node_t *temp=current->next;
add_node(T, current);
current=temp;
}
}
// 时钟指针前进一秒,高层轮定时器逐级下放
// 每到整秒:重映射当前分钟槽
// 每到整分:重映射当前小时槽
// 到达半日周期(回绕):重映射 hour[0] 处理 12 小时回绕
static void
timer_shift(timer_st *T) {
uint32_t ct = ++T->time % HALF_DAY;
// ++T->time 先自增,后返回自增后的值
// T->time++ 先返回原值,后自增
if (ct == 0) { // ct == 0 表示刚好走完一个 12 小时周期
remap(T, T->hour, 0); // 将时轮的第 0 槽(hour[0])中的所有定时器重新映射
} else {
if (ct % SECONDS == 0) { // 每到整分钟时,处理当前小时槽(跳过 hour[0])
// 两个括号 两个作用域 两个作用域中的 idx 是不同的变量,互不影响
{
uint32_t idx = (uint32_t)(ct / ONE_MINUTE) % MINUTES;
if (idx != 0) { // 同样分轮跳过 0 号槽
remap(T, T->minute, idx);// 将分轮的第 idx 槽中的所有定时器重新映射
return; // 这里的return可以保障一次只迁移一个槽,当时轮和分轮同时满足时,分轮优先
}
}// ← idx 变量在这里销毁
{
uint32_t idx = (uint32_t)(ct / ONE_HOUR) % HOURS;
if (idx != 0) { // 同样时轮跳过 0 号槽
remap(T, T->hour, idx);// 将时轮的第 idx 槽(hour[idx])中的所有定时器重新映射
}
}
}
}
}
补充解释
每秒的执行都会将当前秒槽下的任务执行并全部free掉。那怎么实现循环定时呢?每次都重新添加?
是的,要重新添加。还可以直接不释放此任务,然后根据定时时间继续添加到对应的时间槽中。
为什么timer_update会有两次执行?
每次 timer_update 调用会跨越两个秒槽。
这里的两次执行是在59秒进入timer_update函数,执行59秒的任务,然后推进1s,执行60秒的任务。
60秒再次进入timer_update函数,因为在59秒进入timer_update函数时执行了60秒的任务,执行完后就被释放了。所以这里的60秒进入timer_update函数,只会判断一下,发现已经执行完了,就推进一秒,再执行61秒的任务。
其实就是在59s进入timer_update函数执行60s的任务(59s的任务已经在58s进入timer_update函数执行完了),在60s进入timer_update函数执行61s的任务。
定时器在到期时刻的第一时间就被执行,而不是等到该秒结束。可以做到低延迟。
做一次测试,添加58s,59s,60s,61s任务,使用两次执行,运行程序。使用一次执行,运行程序。看一下结果。
如果只有一次执行,都整体延迟了一秒

有两次执行,完美执行

完整代码
clock-timer.c
c
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "spinlock.h"
#include "clock-timer.h"
// 3层时间轮:秒轮(60槽)、分轮(60槽)、时轮(12槽)
// 覆盖12小时周期(43200秒),到期后回绕
#define SECONDS 60
#define MINUTES 60
#define HOURS 12
#define ONE_HOUR 3600
#define ONE_MINUTE 60
#define HALF_DAY 43200 // 最大定时时间 12*3600 12小时
// 单向链表,带头哨兵节点和尾指针,支持 O(1) 追加
typedef struct link_list {
timer_node_t head; // 哨兵节点(非真实定时器)
timer_node_t *tail;
}link_list_t;
// 3层时间轮:随时间推进,定时器从时轮 → 分轮 → 秒轮逐级下放
typedef struct timer {
link_list_t second[SECONDS]; // 秒轮(每槽 1 秒)
link_list_t minute[MINUTES]; // 分轮(每槽 1 分)
link_list_t hour[HOURS]; // 时轮(每槽 1 小时)
spinlock_t lock; // 用于多线程安全操作时间轮
uint32_t time; // 当前时间轮时间(秒,范围 0 ~ HALF_DAY-1)程序启动时为0,每过一秒+1
time_t current_point; // 上次观察到的单调时钟秒数
}timer_st;
// 全局定时器实例(单例)
static timer_st * TI = NULL;
// 将槽中所有节点摘除,重置链表为空,返回第一个节点
// 用于上一级的时间轮映射到下一级时间轮中
static timer_node_t *
link_clear(link_list_t *list) {
timer_node_t * ret = list->head.next;
list->head.next = 0;
list->tail = &(list->head);
return ret;
}
// O(1) 追加到链表尾部,尾插法
static void
link_to(link_list_t *list, timer_node_t *node) {
list->tail->next = node;
list->tail = node;
node->next=0;
}
// 根据剩余时间将定时器分级插入对应的轮层:
// < 60秒 → 秒轮,60~3600秒 → 分轮,> 3600秒 → 时轮
static void
add_node(timer_st *T, timer_node_t *node) {
uint32_t time=node->expire; // 任务到期时间点
uint32_t current_time=T->time; // 当前时间点
uint32_t msec = time - current_time; // 距离任务到期还剩多少时间
// 根据剩余时间进行分级插入
if (msec < ONE_MINUTE) {
link_to(&T->second[time % SECONDS], node);
} else if (msec < ONE_HOUR) {
link_to(&T->minute[(uint32_t)(time/ONE_MINUTE) % MINUTES], node);
} else {
link_to(&T->hour[(uint32_t)(time/ONE_HOUR) % HOURS], node);
}
}
// 将高层槽level第idx槽中的定时器逐级下放到低层轮(时→分→秒)
static void
remap(timer_st *T, link_list_t *level, int idx) {
timer_node_t *current = link_clear(&level[idx]);
while (current) {
timer_node_t *temp=current->next;
add_node(T, current);
current=temp;
}
}
// 时钟指针前进一秒,高层轮定时器逐级下放
// 每到整秒:重映射当前分钟槽
// 每到整分:重映射当前小时槽
// 到达半日周期(回绕):重映射 hour[0] 处理 12 小时回绕
static void
timer_shift(timer_st *T) {
uint32_t ct = ++T->time % HALF_DAY;
// ++T->time 先自增,后返回自增后的值
// T->time++ 先返回原值,后自增
if (ct == 0) { // ct == 0 表示刚好走完一个 12 小时周期
remap(T, T->hour, 0); // 将时轮的第 0 槽(hour[0])中的所有定时器重新映射
} else {
if (ct % SECONDS == 0) { // 每到整分钟时,处理当前小时槽(跳过 hour[0])
// 两个括号 两个作用域 两个作用域中的 idx 是不同的变量,互不影响
{
uint32_t idx = (uint32_t)(ct / ONE_MINUTE) % MINUTES;
if (idx != 0) { // 同样分轮跳过 0 号槽
remap(T, T->minute, idx);// 将分轮的第 idx 槽中的所有定时器重新映射
return; // 这里的return可以保障一次只迁移一个槽,当时轮和分轮同时满足时,分轮优先
}
}// ← idx 变量在这里销毁
{
uint32_t idx = (uint32_t)(ct / ONE_HOUR) % HOURS;
if (idx != 0) { // 同样时轮跳过 0 号槽
remap(T, T->hour, idx);// 将时轮的第 idx 槽(hour[idx])中的所有定时器重新映射
}
}
}
}
}
// 链表中未取消的定时器任务会触发回调函数,然后free;已经取消的定时器任务,会直接free
// 在调用dispatch_list时,必须确保current不为空,使用do-while可以省去一次空判断
// 若使用while会多一次空判断
static void
dispatch_list(timer_node_t *current) {
do {
timer_node_t * temp = current;
current=current->next;
if (temp->cancel == 0) //0:未取消 1:已取消
temp->callback(temp); // 如果未取消,就执行回调函数
free(temp);
} while (current);
}
// 执行当前秒槽中所有到期的定时器
// 每秒调用一次
static void
timer_execute(timer_st *T) {
uint32_t idx = T->time % SECONDS;
while (T->second[idx].head.next) {
timer_node_t *current = link_clear(&T->second[idx]); // 取出当前秒槽中的所有定时器节点
spinlock_unlock(&T->lock);
dispatch_list(current);
spinlock_lock(&T->lock);
}
}
// 一次滴答:执行当前槽 → 推进时钟 → 再次执行
// 两次执行是为了处理 shift 后定时器级联落入当前槽的边界情况
static void
timer_update(timer_st *T) {
spinlock_lock(&T->lock);
timer_execute(T); // 第一次执行(当前秒槽)
timer_shift(T); // 时间指针前进1秒+级联迁移
timer_execute(T); // 第二次执行(新秒槽)
spinlock_unlock(&T->lock);
}
// 分配并零初始化时间轮,将所有链表槽初始化为空
static timer_st *
create_timer() {
timer_st *r = (timer_st *)malloc(sizeof(timer_st));
memset(r,0,sizeof(*r));
int i;
for (i=0; i<SECONDS; i++) {
link_clear(&r->second[i]);
}
for (i=0; i<MINUTES; i++) {
link_clear(&r->minute[i]);
}
for (i=0; i<HOURS; i++) {
link_clear(&r->hour[i]);
}
spinlock_init(&r->lock);
return r;
}
void
init_timer(void) {
TI = create_timer();
TI->current_point = now_time(); // 获取初始化时的系统时钟时间
}
timer_node_t*
add_timer(int time, handler_pt func) {
timer_node_t *node = (timer_node_t *)malloc(sizeof(*node));
spinlock_lock(&TI->lock);
node->expire = time+TI->time; // 计算到时时间
printf("add timer at %u, expire at %u, now_time at %lu, expire_time at %lu\n", TI->time, node->expire, now_time(), node->expire + now_time());
node->callback = func;
node->cancel = 0;
// 若 time <= 0,立即触发,不插入时间轮
if (time <= 0) {
spinlock_unlock(&TI->lock);
node->callback(node);
free(node);
return NULL;
}
add_node(TI, node);
spinlock_unlock(&TI->lock);
return node;
}
// 软删除:标记为已取消,节点在其所属槽过期时释放
void
del_timer(timer_node_t *node) {
node->cancel = 1;
}
// 主驱动循环:每 200ms 轮询一次,追赶流逝的秒数并触发定时器
// 自上轮检查以来每流逝一秒,调用一次 timer_update()
void
check_timer(int *stop) {
while (*stop == 0) {
time_t cp = now_time();
if (cp != TI->current_point) { // 检测到系统时钟前进
uint32_t diff = (uint32_t)(cp - TI->current_point);
TI->current_point = cp;
int i;
for (i=0; i<diff; i++) { // 防止时间跳跃导致定时器丢失
timer_update(TI);
}
}
usleep(200000); // 0.2s
}
}
// 释放三轮中所有残留的定时器节点(退出前清理)
void
clear_timer() {
int i;
for (i=0; i<SECONDS; i++) {
link_list_t * list = &TI->second[i];
timer_node_t* current = list->head.next;
while(current) {
timer_node_t * temp = current;
current = current->next;
free(temp);
}
link_clear(&TI->second[i]);
}
for (i=0; i<MINUTES; i++) {
link_list_t * list = &TI->minute[i];
timer_node_t* current = list->head.next;
while(current) {
timer_node_t * temp = current;
current = current->next;
free(temp);
}
link_clear(&TI->minute[i]);
}
for (i=0; i<HOURS; i++) {
link_list_t * list = &TI->hour[i];
timer_node_t* current = list->head.next;
while(current) {
timer_node_t * temp = current;
current = current->next;
free(temp);
}
link_clear(&TI->hour[i]);
}
}
// 单调时钟(秒)------不受系统时间跳变影响
time_t
now_time() {
struct timespec ti;
clock_gettime(CLOCK_MONOTONIC, &ti); // 获取单调递增的系统时钟(不受NTP调整影响)从开机计算
return ti.tv_sec;// ti.tv_sec:秒 ti.tv_nsec:纳秒
}
clock-timer.h
c
#ifndef _MARK_TIMEWHEEL_
#define _MARK_TIMEWHEEL_
#include <time.h>
#include <stdint.h>
#include <stddef.h>
typedef struct timer_node timer_node_t;
typedef void (*handler_pt) (struct timer_node *node); // 回调函数类型
// 定义定时器节点结构体
struct timer_node {
struct timer_node *next; // 链表指针
uint32_t expire; // 到期时间(秒)
handler_pt callback; // 回调函数
uint8_t cancel; // 删除标记 0:未取消 1:已取消
};
void init_timer(void); // 初始化定时器系统
timer_node_t* add_timer(int time, handler_pt func); // 添加定时器,time为相对时间(秒),func为回调函数
void del_timer(timer_node_t *node); // 删除定时器(软删除,标记为已取消)
void check_timer(int *stop); // 主驱动循环,阻塞
void clear_timer(); // 释放所有资源
time_t now_time(); // 获取时钟
#endif
clock-main.c
c
#include <stdio.h>
#include "clock-timer.h"
void do_timer(timer_node_t *node) {
(void)node;
printf("do_timer expired now_time:%lu\n", now_time());
}
int stop = 0;
int main() {
init_timer();
add_timer(3, do_timer);
add_timer(8, do_timer);
add_timer(9, do_timer);
add_timer(10, do_timer);
add_timer(11, do_timer);
add_timer(58, do_timer);
add_timer(59, do_timer);
add_timer(60, do_timer);
add_timer(61, do_timer);
add_timer(62, do_timer);
check_timer(&stop);
clear_timer();
return 0;
}
spinlock.h
c
#ifndef SPINLOCK_H
#define SPINLOCK_H
typedef struct spinlock {
int lock;
}spinlock_t;
void spinlock_init(spinlock_t *lock) {
lock->lock = 0;
}
void spinlock_lock(spinlock_t *lock) {
while (__sync_lock_test_and_set(&lock->lock, 1)) {}
}
int spinlock_trylock(spinlock_t *lock) {
return __sync_lock_test_and_set(&lock->lock, 1) == 0;
}
void spinlock_unlock(spinlock_t *lock) {
__sync_lock_release(&lock->lock);
}
void spinlock_destroy(spinlock_t *lock) {
(void) lock;
}
#endif
运行命令
gcc clock-timer.c clock-main.c -o clock -I./ -lpthread