高性能定时器3——时间轮

在网络程序中我们通常要处理三种事件,网络I/O事件、信号以及定时事件,我们可以使用I/O复用系统调用(select、poll、epoll)将这三类事件进行统一处理。我们通常使用定时器来检测一个客户端的活动状态,服务器程序通常管理着众多定时事件,因此有效地组织这些定时事件,使之能在预期的时间点被触发且不影响服务器的主要逻辑,对于服务器的性能有着至关重要的影响。为此我们需要将每个定时事件分别封装为定时器,并使用某种容器类数据结构,比如:链表、排序链表、最小堆、红黑树以及时间轮等,将所有定时器串联起来,以实现对定时事件的统一管理。此处所说的定时器,确切的说应该是定时容器,定时器容器是容器类数据结构;定时器则是容器内容纳的一个个对象,它是对定时事件的封装,定时容器是用来管理定时器的。

在本文中将主要介绍使用红黑树来实现的定时容器。

1、时间轮简介

一种简单的时间轮如下图所示:
在这里插入图片描述

轮中实线指针指向轮中的一个槽(slot)。它以恒定的速度顺时针转动,每转动一步就指向下一个槽(虚线指针指向的槽),每次转动一次称为一个滴答(tick)。一个滴答的时间称为时间轮的槽间隔si(slot interval),它实际上就是定时器的心搏时间。该时间轮共有N个槽,因此它转一圈所需的时间就是N*si。每个槽中保存了一个定时器链表,时间轮的结构与哈希链表的结构是比较相似的。槽中的每条链表上的定时器具有相同的特征:它们的定时相差N*si的整数倍。时间轮正是利用这个关系将定时器三列到不同的链表中的。

假如现在指针指向槽cs,我们要添加一个定时时间为ti的定时器,则该定时器将被插入槽ts对应链表中的:
t s = ( c s + ( t i / s i ) ) % N ts = (cs + (ti / si)) \% N ts=(cs+(ti/si))%N
比如每个槽间隔si为100ms,N为600,转动一圈经过的时间为600*100ms,也就是60s。假设现在指针指向槽cs=10;我们添加一个定时时间60s也就是60000ms的定时器,
t s = ( 10 + ( 60000 / 100 ) ) % 600 = 10 ts = (10 + (60000 / 100)) \% 600 = 10 ts=(10+(60000/100))%600=10
也就是转一圈重新回到cs=10时将会触发,如果此时添加的定时时间为120s,也就是转两圈重新回到该位置时触发。因此需要一个变量来保存一个定时器需要转多少圈后才触发。

我们可以与I/O复用系统调用(select、poll、epoll)和时间轮一起来实现定时容器,将时间轮的的槽间隔作为I/O复用系统调用的超时值,当系统调用返回时,就检查当前指向的槽中的定时器,遍历槽中的定时器,如果圈数为0,则说明该定时器到期,执行相应的回调函数。如果圈数大于0,将其减一。遍历完成之后,将指针指向下一个槽并继续上述操作。

时间轮使用哈希表的思想,将定时器散列到不同的链表上,这样每条链表上的定时器就相对比较少。但是对时间轮而言,要提高精度,就要使si足够小。要提高执行效率,则要求N足够大。

2、代码实现如下:

该定时容器的思路是:将槽间隔作为I/O复用系统调用(select、poll、epoll)的超时值,当系统调用返回后就调用tick函数检查当前指针指向的槽的链表中的定时器,如果定时器中保存的圈数变量等于0,说明定时器到期,执行其中的回调函数,并删除该定时器。如果圈数大于0,说明该定时器还未到期,将圈数减一。遍历完成后,将指针指向下一个槽位。继续上述操作。

时间轮定时容器的几个接口介绍:

​ 1) tick :在tick函数中循环查找定时器,如果定时器圈数为0,则定时器到期,执行其回调函数,然后删除该定时器。如果定时器圈数大于0,则将该变量减1。

​ 2)addTimer::向容器中添加一个定时器,并返回定时器的指针。

​ 3)delTimer::根据传入的定时器指针删除容器中的一个定时器,并且销毁资源。

​ 4)resetTimer: 重置一个定时器。

​ 5)getMinExpire:获取槽间隔;

代码如下:

timer_common.hpp

#ifndef _LIB_SRC_TIMER_COMMON_H
#define _LIB_SRC_TIMER_COMMON_H

#include <stdio.h>
#include <sys/time.h>

// 获取时间戳 单位:毫秒
time_t getMSec()
{
   
   
    struct timeval tv;
    gettimeofday(&tv, NULL);
    return tv.tv_sec * 1000 + tv.tv_usec / 1000;
}

// 定时器数据结构的定义
template <typename _User_Data>
class Timer
{
   
   
public:
    Timer() : _user_data(nullptr), _cb_func(nullptr) {
   
   };
    Timer(int msec) : _user_data(nullptr), _cb_func(nullptr)
    {
   
   
        this->_expire = getMSec() + msec;
    }

    ~Timer()
    {
   
   

    }

    void setTimeout(time_t timeout)
    {
   
   
        this->_expire = getMSec() + timeout;
    }

    time_t getExpire()
    {
   
   
        return _expire;
    }

    void setUserData(_User_Data *userData)
    {
   
   
        this->_user_data = userData;
    }

    void handleTimeOut()
    {
   
   
        if(_cb_func)
        {
   
   
            _cb_func(_user_data);
        }
        
    }

    using TimeOutCbFunc = void (*)(_User_Data *);
    void setCallBack(TimeOutCbFunc callBack)
    {
   
   
        this->_cb_func = callBack;
    }

private:
    time_t _expire;                    // 定时器生效的绝对时间            
    _User_Data *_user_data;            // 用户数据
    TimeOutCbFunc _cb_func;           // 超时时的回调函数
};



template <typename _UData>
class ITimerContainer 
{
   
   
public:
    ITimerContainer() = default;
    virtual ~ITimerContainer() = default;

public:
    virtual void tick() = 0;               
    virtual Timer<_UData> *addTimer(time_t timeout) = 0;
    virtual void delTimer(Timer<_UData> *timer) = 0;
    virtual void resetTimer(Timer<_UData> *timer, time_t timeout) = 0;
    virtual int getMinExpire() = 0;
};


#endif

time_wheel_timer.hpp

#ifndef _LIB_SRC_TIME_WHEEL_TIMER_H_
#define _LIB_SRC_TIME_WHEEL_TIMER_H_

#include "timer_common.hpp"
#include <array>
#include <list>
#include <iostream>

/*
 * @Author: MGH
 * @Date: 2021-09-29 12:57:45
 * @Last Modified by: Author
 * @Last Modified time: 2021-09-29 12:57:45
 * @Description: Time Wheel Timer
*/

template <typename _UData>
class TimerNode
{
   
   
public:
    TimerNode() = default;
    ~TimerNode() = default;

public:
    void setTimeSlot(int slot)
    {
   
   
        this->_time_slot = slot;
    }

    int getTimeSlot()
    {
   
   
        return this->_time_slot;
    }

    void setRotation(int rotation)
    {
   
   
        this->_rotation = rotation;
    }

    int getRotation()
    {
   
   
        return this->_rotation;
    }

public
在Java中,我们可以使用最小堆来实现高性能定时器。最小堆是一种数据结构,其中根节点的值始终小于或等于其子节点的值。在使用最小堆实现定时器时,我们可以将定时器对象按照超时时间的大小进行排序,并将其插入到最小堆中。这样,我们可以快速地找到超时时间最小的定时器,并在每次tick函数被调用时处理该定时器。 具体来说,我们可以将定时器对象封装成一个类,并在其中存储超时时间、回调函数等信息。每当一个定时器到期时,我们可以执行相应的回调函数,并从最小堆中删除该定时器。随着时间的推移,我们只需要检查最小堆的根节点即可确定下一个最近的超时时间。这种实现方式可以有效地管理大量的定时器,并且对服务器的性能有着重要的影响。 请注意,Java中已经提供了一些现成的高性能定时器框架,比如Netty的定时器模块和Quartz等。这些框架已经实现了高效的定时器管理,并且在实际项目中被广泛使用。你可以根据自己的需求选择适合的框架来实现高性能定时器功能。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [高性能定时器------------时间堆](https://blog.youkuaiyun.com/destory27/article/details/81774291)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [高性能定时器3——时间轮](https://blog.youkuaiyun.com/Peerless__/article/details/120675515)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值