定时器之多层级时间轮
定时器应用
- 心跳检测
- 倒计时
- 武器/技能冷却
- 其它需要使用超时机制的功能
数据结构
实现定时器常见的数据结构有红黑树,最小堆,跳表,时间轮。这里需要特别说明的是对于最小堆,我们需要借助map来快速索引到节点。
实现代码
此处用时间轮来实现定时器
spinlock.h
#ifndef SPINLOCK_H
#define SPINLOCK_H
struct spinlock {
int lock;
};
void spinlock_init(struct spinlock *lock) {
lock->lock = 0;
}
void spinlock_lock(struct spinlock *lock) {
//原子操作,将1写入&lock->lock,同时返回 &lock->lock 之前的值
while (__sync_lock_test_and_set(&lock->lock, 1)) {}
}
int spinlock_trylock(struct spinlock *lock) {
return __sync_lock_test_and_set(&lock->lock, 1) == 0;
}
void spinlock_unlock(struct spinlock *lock) {
__sync_lock_release(&lock->lock);
}
void spinlock_destroy(struct spinlock *lock) {
(void) lock;
}
#endif
timewheel.h
#ifndef _MARK_TIMEWHEEL_
#define _MARK_TIMEWHEEL_
#include <stdint.h>
#include <assert.h>
#include <stdio.h>
#define TIME_NEAR_SHIFT 8
#define TIME_NEAR (1 << TIME_NEAR_SHIFT)
#define TIME_LEVEL_SHIFT 6
#define TIME_LEVEL (1 << TIME_LEVEL_SHIFT)
#define TIME_NEAR_MASK (TIME_NEAR-1)
#define TIME_LEVEL_MASK (TIME_LEVEL-1)
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;//定时器是否删除,1删除不执行
uint8_t repeat; //是否重复执行 1 重复执行
uint32_t timing;//定时时间
int id; // 代表定时器节点id
};
// 添加定时器
timer_node_t* add_timer(int time, handler_pt func, int threadid,uint8_t repeat);
//时间轮定时任务检测
void expire_timer(void);
// 删除定时器
void del_timer(timer_node_t* node);
// 初始化定时器
void init_timer(void);
// 清除定时器
void clear_timer();
#endif
timewheel.c
#include "spinlock.h"
#include "timewheel.h"
#include <string.h>
#include <stddef.h>
#include <stdlib.h>
#if defined(__APPLE__)
#include <AvailabilityMacros.h>
#include <sys/time.h>
#include <mach/task.h>
#include <mach/mach.h>
#else
#include <time.h>
#endif
//每次映射是将下层单元格数据映射到上一层
//数组每个元素的,和单时间轮一样
typedef struct link_list {
timer_node_t head;
timer_node_t *tail;
}link_list_t;
//多层时间轮 5个层级
typedef struct timer {
link_list_t near[TIME_NEAR];//第一个层级 2^8 = 256
link_list_t t[4][TIME_LEVEL];//2-5 层 2^6 = 64
struct spinlock lock;
uint32_t time;
uint64_t current;
uint64_t current_point;//当前时间点,基本单位10ms
}s_timer_t;
static s_timer_t * TI = NULL;
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;
}
//节点映射,尾插法
void
link(link_list_t *list, timer_node_t *node) {
list->tail->next = node;
list->tail = node;
node->next=0;
}
//将时间事件节点插入时间轮
void
add_node(s_timer_t *T, timer_node_t *node) {
uint32_t time=node->expire;//过期时间 uint32_t 正好int类型 4个字节32位
uint32_t current_time=T->time;//当前时间点
uint32_t msec = time - current_time;//还有多久要被执行任务的时间
if (msec < TIME_NEAR) { //[0, 0x100) 第一层级
link(&T->near[time&TIME_NEAR_MASK],node); //time%256 在[0,255] 数组映射
} else if (msec < (1 << (TIME_NEAR_SHIFT+TIME_LEVEL_SHIFT))) {//[0x100, 0x4000) 第二层级
link(&T->t[0][((time>>TIME_NEAR_SHIFT) & TIME_LEVEL_MASK)],node);
} else if (msec < (1 << (TIME_NEAR_SHIFT+2*TIME_LEVEL_SHIFT))) {//[0x4000, 0x100000)
link(&T->t[1][((time>>(TIME_NEAR_SHIFT + TIME_LEVEL_SHIFT)) & TIME_LEVEL_MASK)],node);
} else if (msec < (1 << (TIME_NEAR_SHIFT+3*TIME_LEVEL_SHIFT))) {//[0x100000, 0x4000000)
link(&T->t[2][((time>>(TIME_NEAR_SHIFT + 2*TIME_LEVEL_SHIFT)) & TIME_LEVEL_MASK)],node);
} else {//[0x4000000, 0xffffffff]
link(&T->t[3][((time>>(TIME_NEAR_SHIFT + 3*TIME_LEVEL_SHIFT)) & TIME_LEVEL_MASK)],node);
}
}
//定时器添加
timer_node_t*
add_timer(int time, handler_pt func, int threadid,uint8_t repeat) {
timer_node_t *node = (timer_node_t *)malloc(sizeof(*node));
spinlock_lock(&TI->lock);
node->timing = time;
node->expire = time+TI->time;// 每10ms加1 0
node->callback = func;
node->id = threadid;
node->repeat = repeat;
if (time <= 0) {
node->callback(node);
free(node);
spinlock_unlock(&TI->lock);
return NULL;
}
add_node(TI, node);
spinlock_unlock(&TI->lock);
printf("add time event success\n");
return node;
}
//这⾥发⽣重新映射,将i+1层级idx格⼦中的定时任务重新映射到i层级中
void
move_list(s_timer_t *T, int level, int idx) {
timer_node_t *current = link_clear(&T->t[level][idx]);
while (current) {
timer_node_t *temp=current->next;
add_node(T,current);
current=temp;
}
}
//由下往上重新映射
void
timer_shift(s_timer_t *T) {
int mask = TIME_NEAR;//256
uint32_t ct = ++T->time; // ++ ⼀次代表10ms,可以认为时间相对于服务运行时间
if (ct == 0) {//溢出
move_list(T, 3, 0);
} else {
// ct / 256
uint32_t time = ct >> TIME_NEAR_SHIFT;
int i=0;
// ct % 256 == 0 说明是否移动到了 不同层级的 最后⼀格
while ((ct & (mask-1))==0) {
int idx=time & TIME_LEVEL_MASK;
if (idx!=0) {
move_list(T, i, idx);//这⾥发⽣重新映射,将i+1层级idx格⼦中的定时任务重新映射到i层级中
break;
}
mask <<= TIME_LEVEL_SHIFT;
time >>= TIME_LEVEL_SHIFT;
++i;
}
}
}
//执行层级1 数组中每个元素对应的链表定时器
void
dispatch_list(timer_node_t *current) {
do {
timer_node_t * temp = current;
current=current->next;
if (temp->cancel == 0)
{
temp->callback(temp);
if(temp->repeat == 1){
//重新注册就可以了
add_timer(temp->timing,temp->callback,temp->id,temp->repeat);
}
}
free(temp);
} while (current);
}
//执行定时器
void
timer_execute(s_timer_t *T) {
//执行层级1 的定时器
int idx = T->time & TIME_NEAR_MASK;// T->time % 255
//遍历层级1的定时器
while (T->near[idx].head.next) {
timer_node_t *current = link_clear(&T->near[idx]);
spinlock_unlock(&T->lock);
dispatch_list(current);
spinlock_lock(&T->lock);
}
}
//执行定时器并重新映射
void
timer_update(s_timer_t *T) {
spinlock_lock(&T->lock);
timer_execute(T);
timer_shift(T);
timer_execute(T);
spinlock_unlock(&T->lock);
}
void
del_timer(timer_node_t *node) {
node->cancel = 1;
}
//创建时间轮
s_timer_t *
timer_create_timer() {
s_timer_t *r=(s_timer_t *)malloc(sizeof(s_timer_t));
memset(r,0,sizeof(*r));
int i,j;
//第一层数组
for (i=0;i<TIME_NEAR;i++) {
link_clear(&r->near[i]);
}
//第2-5层数组
for (i=0;i<4;i++) {
for (j=0;j<TIME_LEVEL;j++) {
link_clear(&r->t[i][j]);
}
}
spinlock_init(&r->lock);
r->current = 0;
return r;
}
//时间间隔10ms
uint64_t
gettime() {
uint64_t t;
#if !defined(__APPLE__) || defined(AVAILABLE_MAC_OS_X_VERSION_10_12_AND_LATER)
struct timespec ti;
clock_gettime(CLOCK_MONOTONIC, &ti);
t = (uint64_t)ti.tv_sec * 100;
t += ti.tv_nsec / 10000000;
#else
struct timeval tv;
gettimeofday(&tv, NULL);
t = (uint64_t)tv.tv_sec * 100;
t += tv.tv_usec / 10000;
#endif
return t;
}
void
expire_timer(void) {
uint64_t cp = gettime();
if (cp != TI->current_point) {
uint32_t diff = (uint32_t)(cp - TI->current_point);//diff 单位10ms
TI->current_point = cp;
int i;
//10ms 轮询一次
for (i=0; i<diff; i++) {
timer_update(TI);
}
}
}
void
init_timer(void) {
TI = timer_create_timer();
TI->current_point = gettime();
}
void
clear_timer() {
int i,j;
for (i=0;i<TIME_NEAR;i++) {
link_list_t * list = &TI->near[i];
timer_node_t* current = list->head.next;
while(current) {
timer_node_t * temp = current;
current = current->next;
free(temp);
}
link_clear(&TI->near[i]);
}
for (i=0;i<4;i++) {
for (j=0;j<TIME_LEVEL;j++) {
link_list_t * list = &TI->t[i][j];
timer_node_t* current = list->head.next;
while (current) {
timer_node_t * temp = current;
current = current->next;
free(temp);
}
link_clear(&TI->t[i][j]);
}
}
}
测试代码 main.c
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <time.h>
#include <stdlib.h>
#include "timewheel.h"
//时间轮 多线程环境下锁的操作粒度小
struct context {
int quit;//1 退出
int thread;//线程数
};
struct thread_param {
struct context *ctx;
int id;//标识线程id
};
static struct context ctx = {0};
void do_timer(timer_node_t *node) {
printf("timer expired:%d - thread-id:%d\n", node->expire, node->id);
}
//线程工作
void* thread_worker(void *p) {
struct thread_param *tp = p;
int id = tp->id;
struct context *ctx = tp->ctx;
//线程没有停止,一直运行
while (!ctx->quit) {
int expire = rand() % 200;// [0,199]
add_timer(expire, do_timer,id,0);
usleep(expire*(10-1)*1000);
}
printf("thread_worker:%d exit!\n", id);
return NULL;
}
void do_quit(timer_node_t * node) {
ctx.quit = 1;
}
void do_test(timer_node_t *node){
printf("执行定时器%d,每%d执行一次\n",node->id,node->timing*10/1000);
}
int main() {
#if 1
srand(time(NULL));
ctx.thread = 8;
pthread_t pid[ctx.thread];
init_timer();
add_timer(6000, do_quit, 100,0);//60s 后退出 6000*10/ 1000 = 60s
struct thread_param task_thread_p[ctx.thread];
int i;
for (i = 0; i < ctx.thread; i++) {
task_thread_p[i].id = i;
task_thread_p[i].ctx = &ctx;
if (pthread_create(&pid[i], NULL, thread_worker, &task_thread_p[i])) {
fprintf(stderr, "create thread failed\n");
exit(1);
}
}
while (!ctx.quit) {
expire_timer();
usleep(2500);
}
clear_timer();
for (i = 0; i < ctx.thread; i++) {
pthread_join(pid[i], NULL);
}
printf("all thread is closed\n");
#else
srand(time(NULL));
init_timer();
add_timer(500, do_test, 100,1);
add_timer(600, do_test, 101,1);
while (!0) {
expire_timer();
usleep(2500);
}
clear_timer();
#endif
return 0;
}