在上一篇基于升序链表的定时器踢掉空闲连接中,添加定时器的时间复杂度为O(n),删除定时器的时间复杂度为O(1),执行定时任务的时间复杂度为O(1),可以看出添加定时器的执行效率很低,下面讨论的时间轮定时器管理工具可以有效的解决这个问题。
时间轮示例: 在上图所示的时间轮内,指针(实线)指向轮子上的一个槽(slot)。 它以恒定的速度顺时针转动,每转动一步就指向下一个槽(虚线指针指向的槽),每次转动称为一个滴答(tick)。一个滴答的时间称为时间轮的槽间隔si ( slot interval),它实际上就是心搏时间。 该时间轮共有N个槽,因此它运转一周的时间是N * si。
每个槽指向一条定时器链表,每条链表上的定时器具有相同的特征:它们的定时时间相差N * si的整数倍。时间轮正是利用这个关系将定时器散列到不同的链表中。假如现在指针指向槽cs,我们要添加-个定时时间为ti的定时器,则该定时器将被插人槽ts ( timer slot)对应的链表中:
ts= (cs + (ti / si)) % N
基于排序链表的定时器使用唯一的一条链表来管理所有定时器,所以插入操作的效率随着定时器数目的增多而降低。而时间轮使用哈希表的思想,将定时器散列到不同的链表上。这样每条链表上的定时器数目都将明显少于原来的排序链表上的定时器数目,插入操作的效率基本不受定时器数目的影响。很显然,对时间轮而言,要提高定时精度,就要使si值足够小:要提高执行效率,则要求N值足够大。
上图描述的是一种简单的时间轮,因为它只有一个轮子。而复杂的时间轮可能有多个轮子,不同的轮子拥有不同的粒度。相邻的两个轮子,精度高的转一圈,精度低的仅往前移动一槽,就像水表一样。下面将按照上图来编写一个较为简单的时间轮实现代码。
#ifndef TIME_WHEEL_TIMER
#define TIME_WHEEL_TIMER
#include <time.h>
#include <netinet/in.h>
#include <stdio.h>
#define BUFFER_SIZE 64
class tw_timer;
struct client_data
{
sockaddr_in address;
int sockfd;
char buf[ BUFFER_SIZE ];
tw_timer* timer;
};
/* 定时器类 */
class tw_timer
{
public:
tw_timer( int rot, int ts )
: next( NULL ), prev( NULL ), rotation( rot ), time_slot( ts ){
}
public:
int rotation; //记录此定时器在时间轮转多少圈后生效
int time_slot; //记录定时器属于哪个槽
void (*cb_func)( client_data* ); //定时器回调函数
client_data* user_data; //用户数据
tw_timer* next