高性能定时器——时间轮

在上一篇基于升序链表的定时器踢掉空闲连接中,添加定时器的时间复杂度为O(n),删除定时器的时间复杂度为O(1),执行定时任务的时间复杂度为O(1),可以看出添加定时器的执行效率很低,下面讨论的时间轮定时器管理工具可以有效的解决这个问题。

时间轮示例:高性能定时器——时间轮_第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 
#include 
#include 

#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;
    tw_timer* prev;
};

/* 时间轮 */
class time_wheel
{
private:
    static const int N = 60;	//时间轮大小
    static const int TI = 1; 	//槽间隔。即滴答时间
    tw_timer* slots[N];			//时间轮的槽
    int cur_slot;				//时间轮指针当前指向的槽
public:
    time_wheel() : cur_slot( 0 )
    {
        for( int i = 0; i < N; ++i )	//初始化每个槽
        {
            slots[i] = NULL;
        }
    }
    ~time_wheel()
    {
        for( int i = 0; i < N; ++i )	//遍历每个槽销毁其中的定时器
        {
            tw_timer* tmp = slots[i];
            while( tmp )
            {
                slots[i] = tmp->next;
                delete tmp;
                tmp = slots[i];
            }
        }
    }
    tw_timer* add_timer( int timeout )
    {
        if( timeout < 0 )
        {
            return NULL;
        }
        int ticks = 0;
        if( timeout < TI )	//如果时长不足一个滴答则将其提升为一个滴答
        {
            ticks = 1;
        }
        else
        {
            ticks = timeout / TI;	//计算timeout时间需要多少个滴答
        }
		
        int rotation = ticks / N;	//该定时器在多少圈之后生效
        int ts = ( cur_slot + ( ticks % N ) ) % N;		//该定时器位于哪个槽
		
		//创建一个定时器,它在rotation圈后被触发,且位于ts槽上
        tw_timer* timer = new tw_timer( rotation, ts );
        if( !slots[ts] )
        {
            printf( "add timer, rotation is %d, ts is %d, cur_slot is %d\n", rotation, ts, cur_slot );
            slots[ts] = timer;
        }
        else
        {
            timer->next = slots[ts];
            slots[ts]->prev = timer;
            slots[ts] = timer;
        }
        return timer;
    }
    void del_timer( tw_timer* timer )
    {
        if( !timer )
        {
            return;
        }
        int ts = timer->time_slot;
        if( timer == slots[ts] )
        {
            slots[ts] = slots[ts]->next;
            if( slots[ts] )
            {
                slots[ts]->prev = NULL;
            }
            delete timer;
        }
        else
        {
            timer->prev->next = timer->next;
            if( timer->next )
            {
                timer->next->prev = timer->prev;
            }
            delete timer;
        }
    }
	
	/* 每一个滴答(SI)调用一次该函数处理到期的定时器,这个关键的函数 */
    void tick()
    {
        tw_timer* tmp = slots[cur_slot];
		cur_slot = (cur_slot + 1) % N;		//时间轮指针指向下一个槽
		
        printf( "current slot is %d\n", cur_slot );
        while( tmp )
        {
            printf( "tick the timer once\n" );
			
			//rotation>0 说明该定时器在这一轮不起作用
            if( tmp->rotation > 0 )
            {
                tmp->rotation--;
                tmp = tmp->next;
            }
            else
            {
                tmp->cb_func( tmp->user_data );
                if( tmp == slots[cur_slot] )
                {
                    printf( "delete header in cur_slot\n" );
                    slots[cur_slot] = tmp->next;
                    delete tmp;
                    if( slots[cur_slot] )
                    {
                        slots[cur_slot]->prev = NULL;
                    }
                    tmp = slots[cur_slot];
                }
                else
                {
                    tmp->prev->next = tmp->next;
                    if( tmp->next )
                    {
                        tmp->next->prev = tmp->prev;
                    }
                    tw_timer* tmp2 = tmp->next;
                    delete tmp;
                    tmp = tmp2;
                }
            }
        }
    }
};

#endif

对于时间轮而言,添加一个定时器的时间复杂度为O(1),删除一个定时器的时间复杂度为O(1),执行一个定时器的时间复杂度为O(n),但实际情况要比O(n)好得多,因为时间轮将定时器散列到了不同的链表上。

读者不妨将时间轮运用到上一篇基于升序链表的定时器踢掉空闲连接中,观察时间轮的运行机制。

更多网络编程技巧请查看:https://github.com/inmail/matelib

你可能感兴趣的:(网络编程)