定时器的设计与实现
定时器概述
定时器是服务器端和客户端都常用且重要的组件,主要的作用有延迟处理,定时任务,连接检测等相关使用超时机制的功能。
对于服务端来说,驱动服务端逻辑的事件主要有两个,⼀个是网络事件,另⼀个是时间事件;
在不同框架中,这两种事件有不同的实现方式;
-
1.网络事件和时间事件在⼀个线程当中配合使⽤;例如nginx、redis;
-
2.网络事件和时间事件在不同线程当中处理;例如skynet;
定时器功能分析
对于定时器,重要的接口方法有以下几个:
// 初始化定时器
void init_timer();
// 添加定时器
Node* add_timer(int expire, callback cb);
// 删除定时器
bool del_timer(Node* node);
// 找到最近要发⽣的定时任务
Node* find_nearest_timer();
// 更新检测定时器
void update_timer();
// 清除定时器
// void clear_timer();
以上,我们必须实现的接口是增加、删除、查找最小节点,由此对于节点数据结构的实现,有以下几种选择:
数据结构的选择
链表
- 增删o(n),redis定时器使用了无序链表(可替换为跳表)
红黑树
- 一种特殊的平衡二叉搜索树,平衡的是黑节点的高度,对于增删查,时间复杂度为o(logn);对于红黑树最⼩节点为最左侧节点,时间复杂度为o(logn)。 nginx中使用红黑树实现定时器
- stl map set使用红黑树实现,插入相同key时更新元素;定时器不要使用map 结构来实现,因为可能会有相同的key插入,应不更新插入多个节点
最小堆
- 完全二叉树,某一个节点的值总是小于等于它的子节点的值,且堆中的每个节点的子树都是最小堆
- 插入删除 o(logn), 查找最近发生任务o(1)
- Boost.asio(二叉树最小堆)、 go(四叉树最小堆)
跳表
- 增删查概率上o(logn),理想情况下接近二分查找,大量数据时(如256)满足,查找最小节点o(1)。redis可以改造为跳表实现定时器
多线程环境下,红黑树,最小堆,跳表都需要对整个结构加锁,锁的粒度较大
时间轮
-
由于基于升序链表的定时器添加删除的效率较低,而时间轮使用哈希表的思想,将定时器散列到不同的链表上。这样每条链表上的定时器数目都将明显少于原来排序链表上的定时器数目,插入操作的效率较少受定时器数目的影响。
-
时间轮以一个定长数组来模拟时钟秒表的运转,数组的长度N即为时间轮槽(slot)的数目,若精度为1s,则槽间隔(slotInterval)为1。时间轮以恒定的速度顺时针转动,每转动一步就指向下一个槽,每次转动为一个tick。
-
假如现在指针指向槽curSlot,我们要添加一个定时时间为ti的定时器,则该定时器映射到的槽位ts为:
- ts = (curSlot + (ti/slotInterval)) % N
-
显然,对于时间轮而言,要提高定时精度,就要使slotInterval足够小,要提高执行效率,则要求槽数N足够大
多线程环境下,时间轮仅需对某一个槽加锁,锁粒度较小
#include <stdint.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;
int id; // 此时携带参数
};
timer_node_t* add_timer(int time, handler_pt func, int threadid);
void expire_timer(void);
void del_timer(timer_node_t* node);
void init_timer(void);
void clear_timer();
#include "spinlock.h"
#include "timewheel.h"
#include <string.h>
#include <stddef.h>
#include <stdlib.h>
#include <time.h>
typedef struct link_list {
timer_node_t head;
timer_node_t *tail;
}link_list_t;
typedef struct timer {
link_list_t near[TIME_NEAR];
link_list_t t[4][TIME_LEVEL];
struct spinlock lock;
uint32_t time;
uint64_t current;
uint64_t current_point;
}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 current_time=T->time;
uint32_t msec = time - current_time;
if (msec < TIME_NEAR) { //[0, 0x100)
link(&T->near[time&TIME_NEAR_MASK],node);
} 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) {
timer_node_t *node = (timer_node_t *)malloc(sizeof(*node));
spinlock_lock(&TI->lock);
node->expire = time+TI->time;// 每10ms加1 0
node->callback = func;
node->id = threadid;
if (time <= 0) {
node->callback(node);
free(node);
spinlock_unlock(&TI->lock);
return NULL;
}
add_node(TI, node);
spinlock_unlock(&TI->lock);
return node;
}
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;
uint32_t ct = ++T->time;
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);
break;
}
mask <<= TIME_LEVEL_SHIFT;
time >>= TIME_LEVEL_SHIFT;
++i;
}
}
}
void dispatch_list(timer_node_t *current) {
do {
timer_node_t * temp = current;
current=current->next;
if (temp->cancel == 0)
temp->callback(temp);
free(temp);
} while (current);
}
void timer_execute(s_timer_t *T) {
int idx = T->time & TIME_NEAR_MASK;
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]);
}
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;
}
uint64_t gettime() {
uint64_t t;
struct timespec ti;
clock_gettime(CLOCK_MONOTONIC, &ti);
t = (uint64_t)ti.tv_sec * 100;
t += ti.tv_nsec / 10000000;
return t;
}
void expire_timer(void) {
uint64_t cp = gettime();
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);
}
}
}
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]);
}
}
}