【C/C++】五层时间轮的设计与实现

1.概述

网络程序需要处理的一种事件就是定时事件,比如定期检测一个客户端的连接活动状态。服务器程序通常需要管理多个定时任务,因此有效地组织这些定时事件,使之能在预期的时间内被触发且不影响服务器的主要实现,对于服务器的性能有着很重要的影响,为此我们要将每个定时任务封装成定时器。
封装定时器可以使用很多数据结构,链表,排序链表,红黑树,堆,时间轮都可以。本文主要是讲述一个较为高效的定时器容器——时间轮。

2.原理

2.1.单层时间轮示意

时间轮算法借鉴了生活中时钟的例子,并依据Hash表的思想,将定时器散列到不同的链表上,如下图所示,时间轮内有着许多的槽,每一个槽都是一个链表,在设立触发事件的时间并插入到时间轮时,程序会根据设定的时间和当前指针的位置来看插入到哪个槽内。数学公式如下:

ts = (cs + (ti/si)) %N

其中,ts是插入的槽的位置,si是当前指针转动一步的时间间隔,ti是定时器的时间,N是槽总数。

在确定插入的槽的位置时,定时任务会被插入到该槽内的链表中。删除也是同理,根据定时任务的时间确定其所在槽的位置,并删除其所在链表内的定时器。

图中有一个指针,这个指针要以恒定的速度顺时针转动,每转动一步就要指向下一个槽,每次转动都称为一个Tick,当指针转动到有task的槽的时候,程序将会遍历这个槽的链表并将链表内的定时任务迁移/触发。

很显然,对于时间轮而言,要提高精度,就得使得si足够小;要使得时间轮的执行效率足够大,就要保证N的值足够大。

4a6a0e1fafaa4c18bba2738d9df022dc

1.2.多级时间轮

前面讲到对于一个单层时间轮来说,要提高精度,就得使得si足够小;要使得时间轮的执行效率足够大,就要保证N的值足够大。但是槽数过多会使得空间占用非常大,比如我要做一个时间轮(20ms级别的),时间轮延时最大时间是30天,那么既要保证高精度,又要保证效率,我就得设计一个槽数为518400000,这是非常占用空间的。为了解决这个问题,多级时间轮就诞生了。如下图所示,这里设立了两个时间轮,当毫秒轮指针转完一圈,秒轮指针就转一格。

368aa1cb1b6242e2a3e55cf1957576de

当上一级时间轮的指针指向了含有定时器的槽的时候,程序会将该槽的定时器迁移到下一层时间轮内,具体步骤如下:

  1. 将原定时任务的时间减去该槽的index和 tt 的乘积(tt指的是该时间轮一个单位的量和最底层时间轮一个单位的量的商,比如有三个时间轮,小时轮,秒轮和毫秒轮,那么小时轮的tt就是360000,秒轮的tt就是1000)。
  2. 按照剩余时间的大小判断要插入到哪个时间轮,哪个槽内。

这样就可以大大增加精度和执行效率,同时空间也会节省很多。(上面那个时间轮要用518400000个槽,五层时间轮我只用224个。)

3. 时间轮的实现

本章讲述的是实现时间轮的思路和代码,该时间轮是一个五层的时间轮,层级分别为日轮(0-30天),时轮(0-24时间),分钟轮(0-60分钟),秒轮(0-60秒),毫秒轮(0-1000ms,20ms一格)。由于最低的时间轮是毫秒轮,因此tt也是以毫秒为单位的,下面是一些声明

TimeWheelCommonality.hpp

#define MILLISECONDS 20
#define SECONDS 1000
#define MINUTE 60000
#define HOURS 3600000
#define DAILY 86400000
#define MONTH 2592000000

该时间轮是由7个类组成的,第一个TimeWheelCommonality,用来管理时间轮的删除和插入等操作的,它有五个子类,也就是五个时间轮。最后五个时间轮由一个时间轮管理器TimeWheel5管理(5仅仅是版本号-_-),设计的UML类图如下所示:

时间轮的UML类图

TimeWheel

3.1. 定时器类

第一章说到,时间轮只是一个包装定时器的容器,没有定时器,时间轮也仅仅是一个空壳,下面我们就要做一个定时器,定时器类的主要元素如下面的代码所示:

ClientAndTimer.hpp

#include <iostream>
#include <cstring>
#include <cstdlib>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <chrono>
#include <cmath>
#include <vector>
#include <forward_list>
#include <memory>
#include <functional>

using namespace std;

class ClientData;
class TimeWheelData;

/*枚举类型,用来指示时间轮的类型的*/
enum TimeWheelType{
    DAILYWHEEL,
    HOURSWHEEL,
    MINUTEWHEEL,
    SECONDSWHEEL,
    MILLISECONDSWHEEL
};

/*客户类,用来存储客户的信息的*/
class ClientData{
public:
    string clientName;								//用户名
    int fileDescriptor;								//用户连接的文件描述符
    sockaddr_in address;							//地址
    string message;
    unique_ptr<string> writeBuffer;
    shared_ptr<TimeWheelData> timerData;
};

/*定时任务类*/
class TimeWheelData{
public:
    int64_t expireTime;															//设定过期时间
    function<void(shared_ptr<ClientData>)> CallBackFunction;					//定时任务的回调函数
    weak_ptr<ClientData> clientData;											//客户信息
    int currentIndex;															//定时任务的时间槽
    bool isActive;																//定时任务活跃标志,只有true才可以调用回调函数

    friend bool operator==(TimeWheelData& myself, TimeWheelData other);
};

//重载运算符==,用于比较两个TimeWheelData对象是否相同
bool operator==(TimeWheelData& myself, TimeWheelData other){
    return myself.clientData.lock() == other.clientData.lock() && myself.expireTime == other.expireTime;
}

3.2. 时间轮的属性和操作

3.2.1 时间轮的属性

前面讲到了时间轮的原理,它是由一个环状的数组(这里使用vector)构成,每一个槽都是一个链表(使用forward_list),它的属性内容如下:

TimeWheelCommonality.hpp

class TimeWheelCommonality {
private:
    slot_t slots_;							//时间轮
    int currentSlotIndex_;					//当前槽的指针
    int slotNumber_;						//槽数
    TimeWheelType wheelType_;				//时间轮类型
    thread cleanNoneActiveThread_;			//清除非活动任务的线程
    atomic<bool> stopThread_{false};		//停止线程的原子操作

public:
	/*...公有方法*/
}

下面是一些变量类型声明:

typedef vector<unique_ptr<forward_list<shared_ptr<TimeWheelData>>>> slot_t;
typedef unique_ptr<forward_list<shared_ptr<TimeWheelData>>> slot_content_t;

3.2.2.时间轮的插入

在TimeWheelCommonality中,时间轮的插入操作方法AddTimer是一个纯虚函数,具体的实现还得看它子类内的AddTimer,每一个AddTimer的插入逻辑是一样的,大抵分为以下几个步骤(以毫秒轮为例):

  1. 计算该插入的槽
int insertIndex = currentSlotIndex_ + (static_cast<int>(offset) / MILLISECONDS) % slotNumber_;
  1. 查看该槽是否存在过相同的定时任务
auto& slot = slots_[insertIndex];
/*泛型算法,寻找槽里是否有相同的TimeWheelData对象*/
auto it = find_if(
      slot->begin(), 
      slot->end(), 
      [&timer](const shared_ptr<TimeWheelData>& existingTimer) {
                return *existingTimer == *timer;
      }
);
  1. 没有的话就插入,有的话就退出
if (it != slot->end()) {
	cout << "Timer already exists in the slot" << endl;
    return;
}
    
timer->isActive = true;
timer->currentIndex = insertIndex;
slot->push_front(std::move(timer));

代码会在最后展示,每个轮的逻辑都是一样的。

3.2.3. 时间轮的删除

时间轮的删除逻辑较为简单,只需要将timer -> isActive变成false就可以,但是就算是改成了false也只是保证其CallBackFunction不触发,并不能实际删除计时器,为了解决这个问题,本程序有一个删除线程,专门用来删除timer -> isActive == false的定时任务,线程具体代码如下:

TimeWheelCommonality.hpp

/*标记该定时任务为非活动*/
void TimeWheelCommonality::RemarkTimerNotActive(shared_ptr<TimeWheelData>& timer){
    timer -> isActive = false;
}

/*删除非活动定时任务方法*/
void TimeWheelCommonality::DeleteNotActiveTimers(){
	/*加锁保证线程安全*/
    lock_guard<mutex> lock(slotsMutex);
    for(auto& slotptr : slots_){
        if(slotptr){
        	/*泛型算法,移除非活动任务*/
            slotptr -> remove_if(
                [](const shared_ptr<TimeWheelData>& timer){
                    return timer -> isActive == false;
                }
            );
        }
    }
}
/*删除线程方法*/
void TimeWheelCommonality::CleanUpTheNoneActiveSlotsThread(){
    cleanNoneActiveThread_ = thread(
        [this]() -> void{
            while(!stopThread_){
            	/*每10ms运行一次线程*/
                this_thread::sleep_for(chrono::milliseconds(10));
                DeleteNotActiveTimers();
            }
        }
    );
}

这个线程是要在程序开启的同时启动,因此要将其写到构造方法内,具体如下:

TimeWheelCommonality.hpp

/*构造方法*/
TimeWheelCommonality(int slotNumber, TimeWheelType wheelType) : currentSlotIndex_(0), slotNumber_(slotNumber), wheelType_(wheelType) {
	slots_.resize(slotNumber);
    for (int i = 0; i < slotNumber; i++) {
		slots_[i] = make_unique<forward_list<shared_ptr<TimeWheelData>>>();
    }
    /*启动删除线程*/
    CleanUpTheNoneActiveSlotsThread();
}

/*虚析构方法,用来删除线程*/
virtual ~TimeWheelCommonality(){
	stopThread_ = true;
   	if(cleanNoneActiveThread_.joinable()){
    	cleanNoneActiveThread_.join();
    }
}

3.2.4.时间轮的运转

经常被数据结构爱的同学们都知道,vector是一个线性的容器,不可能是环状的,因此要对当前槽指针进行模运算来使得指针归位来模拟“转一圈”的效果。具体实现代码如下:

TimeWheelCommonality.hpp

/*运转方法*/
bool TimeWheelCommonality::RotateTimeWheel() {
    bool advance = false;
    currentSlotIndex_ = (currentSlotIndex_ + 1) % slotNumber_;
    /*如果currentSlotIndex == 0就代表转了一圈*/
    advance = (currentSlotIndex_ == 0);
    return advance;
}

/*你可以用这个来看时间轮是不是转了一圈了*/
bool TimeWheelCommonality::WhetherTimeWheelAdvance() {
    return currentSlotIndex_ == 0;
}

3.2.5.触发任务

触发任务很简单,找到该槽,遍历槽内的链表,判断是不是活动的,是活动的就触发回调函数
具体代码如下所示:

TimeWheelCommonality.hpp

void TimeWheelCommonality::TriggerTask() {
    auto& currentSlot = slots_[currentSlotIndex_];
    if (!currentSlot || currentSlot->empty()) {
        return;
    }
	/*槽内链表的迭代器*/
    auto prev = currentSlot->before_begin();
    auto curr = next(prev);

    while (curr != currentSlot->end()) {
        auto timer = move(*curr);
        /*删去prev迭代器,返回下一个元素*/
        curr = currentSlot->erase_after(prev); 
        /*触发定时任务*/ 
        if (timer && timer->isActive) {
            timer->CallBackFunction(timer->clientData.lock());
            timer->isActive = false;
        }

        prev = curr;
        if (curr != currentSlot->end()) {
            curr = next(curr);
        }
    }
}

4.多层时间轮管理器TimeWheel5

前面的类TimeWheelCommonality只可以管理一个时间轮,要实现五层时间轮的运转,迁移和插入,删除操作,就要使用一个时间轮管理器,下面就是时间轮管理器的操作。

4.1.时间轮的插入和删除操作

时间轮的插入和删除操作依托的就是单个时间轮的插入和删除,步骤都差不多,具体代码如下:

TimeWheel5.hpp
加入多层时间轮方法

// 插入定时器到合适的时间轮
void TimeWheel5::InsertTimeWheel5(shared_ptr<TimeWheelData> timer) {
	/*超时时间*/
    int64_t remainingTime = timer->expireTime;
	
	/*如果大于0ms小于1s*/
    if(remainingTime > 0 && remainingTime < SECONDS) {
    	/*加入毫秒轮*/
        millisecondsTimeWheel_.AddTimer(move(timer));
    }
    /*大于1s小于1min*/
    else if(remainingTime >= SECONDS && remainingTime < MINUTE) {
    	/*加入秒轮*/
        secondsTimeWheel_.AddTimer(move(timer));
    }
    /*大于1min小于1h*/
    else if(remainingTime >= MINUTE && remainingTime < HOURS) {
    	/*加入分钟轮*/
        minuteTimeWheel_.AddTimer(move(timer));
    }
    /*大于1h小于1day*/
    else if(remainingTime >= HOURS && remainingTime < DAILY) {
    	/*加入小时轮*/
        hoursTimeWheel_.AddTimer(move(timer));
    }
     /*大于1day小于1mon*/
    else if(remainingTime >= DAILY && remainingTime < MONTH) {
    	/*加入日轮*/
        dailyTimeWheel_.AddTimer(move(timer));
    }
    /*大于1mon*/
    else if(remainingTime > MONTH) {
        cout << "定时器超过30天,将被插入到天级时间轮(30天后到期)" << endl;
        timer->expireTime = MONTH;
        dailyTimeWheel_.AddTimer(move(timer));
    }
    else {
        cout << "定时器已到期,立即执行回调函数" << endl;
        timer->CallBackFunction(timer->clientData.lock());
    }
}

TimeWheel5.hpp
删掉多层时间轮定时器的方法

// 从时间轮删除定时器
void TimeWheel5::DeleteTimeWheel5(shared_ptr<TimeWheelData> timer) {
    int64_t remainingTime = timer->expireTime;

    if(remainingTime >= MILLISECONDS && remainingTime < SECONDS) {
        millisecondsTimeWheel_.RemarkTimerNotActive(timer);
    }
    else if(remainingTime >= SECONDS && remainingTime < MINUTE) {
        secondsTimeWheel_.RemarkTimerNotActive(timer);
    }
    else if(remainingTime >= MINUTE && remainingTime < HOURS) {
        minuteTimeWheel_.RemarkTimerNotActive(timer);
    }
    else if(remainingTime >= HOURS && remainingTime < DAILY) {
        hoursTimeWheel_.RemarkTimerNotActive(timer);
    }
    else if(remainingTime >= DAILY) {
        dailyTimeWheel_.RemarkTimerNotActive(timer);
    }
    else {
        cout << "定时器过期时间无效" << endl;
    }
}

4.2.时间轮的迁移

我们在插入多层时间轮的时候,是按照它的超时时间来确定插入的时间轮的,随着时间轮的运转,当定时器剩余时间小于该时间轮的单位时间时,就要启动迁移程序,朝着下一级时间轮迁移,具体如

migration

首先我们要做一个函数,能做到将定时任务从一个时间轮迁移到另一个时间轮,本程序使用了一个方法模板,MigrateFromWheelToToWheel来做到这点,具体的内容如下:

TimeWheel5.hpp

/*迁移方法*/
template<class FromTimeWheel>
void TimeWheel5::MigrateTimeWheel_(FromTimeWheel& fromTimeWheel, shared_ptr<TimeWheelData>& timer, int64_t& remainingTime){
	/*将timer的值赋值给newTimer*/
    shared_ptr<TimeWheelData> newTimer = make_shared<TimeWheelData>(*timer);
    /*将剩余时间赋值给newTime -> expireTime*/
    newTimer->expireTime = remainingTime;
    /*重新插入newTimer*/
    InsertTimeWheel5(newTimer);
    /*设置原来的计时器为非活动*/
    fromTimeWheel.RemarkTimerNotActive(timer);
}

下面是使用TickWheel_方法实现时间轮的触发和迁移,TickTimeWheel_其实也是一个模板方法,具体代码如下:

TimeWheel5.hpp

/*触发方法*/
template<class TimeWheel>
void TimeWheel5::TickTimeWheel_(TimeWheel& timeWheel, int64_t timeUnit){
    /*timeWheel.GetSlots()[timeWheel.GetSlotCurrentIndex()]是当前的槽*/
    if(timeWheel.GetSlots()[timeWheel.GetSlotCurrentIndex()] == nullptr || timeWheel.GetSlots()[timeWheel.GetSlotCurrentIndex()]->empty()){
        return;
    }
	/*获取该槽链表的迭代器*/
    slot_iterator_t prev = timeWheel.GetSlots()[timeWheel.GetSlotCurrentIndex()]->before_begin();
    slot_iterator_t iter = timeWheel.GetSlots()[timeWheel.GetSlotCurrentIndex()]->begin();

	/*遍历槽链表,并进行迁移或触发操作*/
    while(iter != timeWheel.GetSlots()[timeWheel.GetSlotCurrentIndex()]->end()){
        shared_ptr<TimeWheelData> timer = *iter;
        int64_t remainingTime = timer->expireTime - timeWheel.GetSlotCurrentIndex() * timeUnit;

        if(remainingTime > 0){
            MigrateTimeWheel_(timeWheel, timer, remainingTime);
            /*迁移/触发完要移除该定时器*/
            iter = timeWheel.GetSlots()[timeWheel.GetSlotCurrentIndex()]->erase_after(prev);
        }
        else {
            if (timer->isActive && timer->CallBackFunction) {
                timer->CallBackFunction(timer->clientData.lock());
            }
            iter = timeWheel.GetSlots()[timeWheel.GetSlotCurrentIndex()]->erase_after(prev);
        }
    }
}

4.3.多层时间轮的转动和触发

第一章讲到时间轮的转动原理:下一级时间轮指针转动一圈,上一级时间轮指针就转动一格,当指针指向有定时器的槽的时候就会触发迁移/触发机制。
多级时间轮的转动是要依托单个时间轮的转动的,单个时间轮的旋转代码第二章就有,不会的再回去研究下
下面是旋转的代码:

// 驱动时间轮运转
void TimeWheel5::RotateTimeWheel5() {
	/*如果时间轮为空就停止旋转*/
    while(!SlotsEmpty_()) {
    	/*
			判断每一级时间轮的是否转过一圈就要用上TimeWheelCommonality的RotateTimeWheel方法。
			当RotateTimeWheel方法为true的时候,就代表下级时间轮准了一圈,这时候就要上一级时间轮转一格
		*/
        bool millisecondsAdvance = millisecondsTimeWheel_.RotateTimeWheel();
        millisecondsTimeWheel_.TriggerTask();
        
        if(millisecondsAdvance) {
            bool secondsAdvance = secondsTimeWheel_.RotateTimeWheel();
            TickTimeWheel_<TimeWheelSeconds>(secondsTimeWheel_, (int64_t)SECONDS);
        
            if(secondsAdvance) {
                bool minuteAdvance = minuteTimeWheel_.RotateTimeWheel();
                TickTimeWheel_<TimeWheelMinute>(minuteTimeWheel_, (int64_t)MINUTE);

                if(minuteAdvance) {
                    bool hoursAdvance = hoursTimeWheel_.RotateTimeWheel();
                    TickTimeWheel_<TimeWheelHours>(hoursTimeWheel_, (int64_t)HOURS);
                    
                    if(hoursAdvance) {
                        bool dailyAdvance = dailyTimeWheel_.RotateTimeWheel();
                        TickTimeWheel_<TimeWheelDaily>(dailyTimeWheel_, (int64_t)DAILY);
                    }
                }
            }
        }
		/*由于这是一个20ms级的时间轮,所以是20ms转动一次*/
        usleep(20000);  
    }
}

再这个旋转代码内有一个SlotsEmpty_()的方法,这是用来查看多级时间轮是否为空的,当五个时间轮都为空的时候,时间轮才会停止转动。
判断单个时间轮是否为空的代码如下:

TimeWheelCommonality.hpp

bool TimeWheelCommonality::IsEmpty(){
    for(int i = 0; i < slotNumber_; i++){
       if(slots_[i] != nullptr && !slots_[i] -> empty()){
            return false;
       }
    }
    return true;
}

判断多级时间轮是否为空的代码如下:

TimeWheel5.hpp

// 检查所有时间轮是否为空
bool TimeWheel5::SlotsEmpty_() {
    return dailyTimeWheel_.IsEmpty() && 
           hoursTimeWheel_.IsEmpty() && 
           minuteTimeWheel_.IsEmpty() && 
           secondsTimeWheel_.IsEmpty() && 
           millisecondsTimeWheel_.IsEmpty();
}

5.源代码

ClientAndTimer.hpp

#include <iostream>
#include <cstring>
#include <cstdlib>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <chrono>
#include <cmath>
#include <vector>
#include <forward_list>
#include <memory>
#include <functional>

using namespace std;

class ClientData;
class TimeWheelData;

/*枚举类,用于判断时间轮的类型*/
enum TimeWheelType{
    DAILYWHEEL,					//日轮
    HOURSWHEEL,					//小时轮
    MINUTEWHEEL,				//分轮
    SECONDSWHEEL,				//秒轮
    MILLISECONDSWHEEL			//毫秒轮
};

//客户信息类
class ClientData{
public:
    string clientName;							//客户名
    int fileDescriptor;							//客户链接套接字
    sockaddr_in address;						//客户链接地址
    string message;								//信息
    unique_ptr<string> writeBuffer;				//写入数据的缓冲区
    shared_ptr<TimeWheelData> timerData;		//定时器的信息
};

class TimeWheelData{
public:
    int64_t expireTime;											//过期时间
    function<void(shared_ptr<ClientData>)> CallBackFunction;	//回调方法
    weak_ptr<ClientData> clientData;							//客户信息
    int currentIndex;											//定时器所在的槽
    bool isActive;												//活跃判断
	/*重载运算符,判断两个定时器对象是否相同*/
    friend bool operator==(TimeWheelData& myself, TimeWheelData other);
};

bool operator==(TimeWheelData& myself, TimeWheelData other){
    return myself.clientData.lock() == other.clientData.lock() && myself.expireTime == other.expireTime;
}

TimeWheelCommonality.hpp

#include <iostream>
#include <cstring>
#include <cstdlib>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <chrono>
#include <cmath>
#include <thread>
#include <mutex>
#include <atomic>
#include <vector>
#include <forward_list>
#include <memory>
#include <functional>
#include <algorithm>
#include "ClientAndTimer.hpp"

#define MILLISECONDS 20
#define SECONDS 1000
#define MINUTE 60000
#define HOURS 3600000
#define DAILY 86400000
#define MONTH 2592000000

using namespace std;
//定义以一个时间轮类型和定时器链表类型,时间轮是一个数组,数组里面是一个个链表
typedef vector<unique_ptr<forward_list<shared_ptr<TimeWheelData>>>> slot_t;
typedef unique_ptr<forward_list<shared_ptr<TimeWheelData>>> slot_content_t;

mutex slotsMutex;		//互斥锁,用于lock_guard

class TimeWheelCommonality {
protected:
    slot_t slots_;							//时间轮
    int currentSlotIndex_;					//时间轮指针
    int slotNumber_;						//时间轮数量
    TimeWheelType wheelType_;				//时间轮类型
    thread cleanNoneActiveThread_;			//清除非活动定时器线程
    atomic<bool> stopThread_{false};		//停止线程的原子操作

public:
	//构造方法
    TimeWheelCommonality(int slotNumber, TimeWheelType wheelType) : currentSlotIndex_(0), slotNumber_(slotNumber), wheelType_(wheelType) {
        slots_.resize(slotNumber);
        for (int i = 0; i < slotNumber; i++) {
            slots_[i] = make_unique<forward_list<shared_ptr<TimeWheelData>>>();
        }
        /*开启构造的同时启动线程*/
        CleanUpTheNoneActiveSlotsThread();
    }
	
	//虚析构方法,用于删除线程
    virtual ~TimeWheelCommonality(){
        stopThread_ = true;
        if(cleanNoneActiveThread_.joinable()){
            cleanNoneActiveThread_.join();
        }
    }

	//获取时间轮
    slot_t& GetSlots() { return slots_; }
	
	//获取时间轮指针
    int GetSlotCurrentIndex() { return currentSlotIndex_; }
	
	//清除非活动定时器线程的方法
    void CleanUpTheNoneActiveSlotsThread();
	
	//判断是否为空
    bool IsEmpty();
	
	//旋转时间轮
    bool RotateTimeWheel();
	
	//是否已经转过一周
    bool WhetherTimeWheelAdvance();
	
	//触发器,触发定时任务
    void TriggerTask();

	//将定时器设定为非活动的
    void RemarkTimerNotActive(shared_ptr<TimeWheelData>& timer);

	//删掉非活动定时器
    void DeleteNotActiveTimers();

	//虚函数:将定时器加入时间轮
    virtual void AddTimer(shared_ptr<TimeWheelData> timer) = 0;
};

bool TimeWheelCommonality::IsEmpty(){
    for(int i = 0; i < slotNumber_; i++){
       if(slots_[i] != nullptr && !slots_[i] -> empty()){
            return false;
       }
    }
    return true;
}

void TimeWheelCommonality::CleanUpTheNoneActiveSlotsThread(){
    cleanNoneActiveThread_ = thread(
        [this]() -> void{
            while(!stopThread_){
            	/*每10ms清理一次非活动定时器*/
                this_thread::sleep_for(chrono::milliseconds(10));
                DeleteNotActiveTimers();
            }
        }
    );
}

bool TimeWheelCommonality::RotateTimeWheel() {
    bool advance = false;
    currentSlotIndex_ = (currentSlotIndex_ + 1) % slotNumber_;
    advance = (currentSlotIndex_ == 0);
    return advance;
}

bool TimeWheelCommonality::WhetherTimeWheelAdvance() {
    return currentSlotIndex_ == 0;
}

void TimeWheelCommonality::TriggerTask() {
	/*获取当前槽*/
    auto& currentSlot = slots_[currentSlotIndex_];
    if (!currentSlot || currentSlot->empty()) {
        return;
    }
	//获取当前槽链表的迭代器
    auto prev = currentSlot->before_begin();
    auto curr = next(prev);
	//遍历链表,触发定时任务
    while (curr != currentSlot->end()) {
    	//先删掉当前定时器
        auto timer = move(*curr);
        curr = currentSlot->erase_after(prev); 
		//触发定时器
        if (timer && timer->isActive) {
            timer->CallBackFunction(timer->clientData.lock());
            timer->isActive = false;
        }
		//移动迭代器
        prev = curr;
        if (curr != currentSlot->end()) {
            curr = next(curr);
        }
    }
}

void TimeWheelCommonality::RemarkTimerNotActive(shared_ptr<TimeWheelData>& timer){
    timer -> isActive = false;
}

void TimeWheelCommonality::DeleteNotActiveTimers(){
	//加锁,保证线程安全
    lock_guard<mutex> lock(slotsMutex);
    for(auto& slotptr : slots_){
        if(slotptr){
        	//泛型算法,移除非活动定时器
            slotptr -> remove_if(
                [](const shared_ptr<TimeWheelData>& timer){
                    return timer -> isActive == false;
                }
            );
        }
    }
}

TimeWheels.hpp

#include "TimeWheelCommonality.hpp"

class TimeWheelDaily : public TimeWheelCommonality{
public:
    TimeWheelDaily() : TimeWheelCommonality(30, TimeWheelType::DAILYWHEEL){}
	
    void AddTimer(shared_ptr<TimeWheelData> timer) override{
        if (!timer || (timer->clientData.lock() == nullptr)) {
            cerr << "This timer is invalid" << endl;
            return;
        }
    	//计算当前超时时间
        int64_t offset = timer->expireTime;
        if (offset < 0) {
            timer->CallBackFunction(timer->clientData.lock());
            return;
        }
    	//得出插入的槽的索引
        int insertIndex = currentSlotIndex_ + (static_cast<int>(offset) / DAILY) % slotNumber_;
        cout << "Insert time wheel successfully" << endl;
    	//查看该定时器是不是之前在这个槽里插入了
        auto& slot = slots_[insertIndex];
        auto it = find_if(
            slot->begin(), 
            slot->end(), 
            [&timer](const shared_ptr<TimeWheelData>& existingTimer) {
                return *existingTimer == *timer;
            }
        );
    
        if (it != slot->end()) {
            cout << "Timer already exists in the slot" << endl;
            return;
        }
    
        timer->isActive = true;
        timer->currentIndex = insertIndex;
        slot->push_front(std::move(timer));
    }
};

class TimeWheelHours : public TimeWheelCommonality{
public:
    TimeWheelHours() : TimeWheelCommonality(24, TimeWheelType::HOURSWHEEL){}

    void AddTimer(shared_ptr<TimeWheelData> timer) override{
        if (!timer || (timer->clientData.lock() == nullptr)) {
            cerr << "This timer is invalid" << endl;
            return;
        }
    
        int64_t offset = timer->expireTime;
        if (offset < 0) {
            timer->CallBackFunction(timer->clientData.lock());
            return;
        }
    
        int insertIndex = currentSlotIndex_ + (static_cast<int>(offset) / HOURS) % slotNumber_;
        cout << "Insert time wheel successfully" << endl;
    
        auto& slot = slots_[insertIndex];
        auto it = find_if(
            slot->begin(), 
            slot->end(), 
            [&timer](const shared_ptr<TimeWheelData>& existingTimer) {
                return *existingTimer == *timer;
            }
        );
    
        if (it != slot->end()) {
            cout << "Timer already exists in the slot" << endl;
            return;
        }
    
        timer->isActive = true;
        timer->currentIndex = insertIndex;
        slot->push_front(std::move(timer));
    }
};

class TimeWheelMinute : public TimeWheelCommonality{
public:
    TimeWheelMinute() : TimeWheelCommonality(60, TimeWheelType::MINUTEWHEEL){}

    void AddTimer(shared_ptr<TimeWheelData> timer) override{
        if (!timer || (timer->clientData.lock() == nullptr)) {
            cerr << "This timer is invalid" << endl;
            return;
        }
    
        int64_t offset = timer->expireTime;
        if (offset < 0) {
            timer->CallBackFunction(timer->clientData.lock());
            return;
        }
    
        int insertIndex = currentSlotIndex_ + (static_cast<int>(offset) / MINUTE) % slotNumber_;
        cout << "Insert time wheel successfully" << endl;
    
        auto& slot = slots_[insertIndex];
        auto it = find_if(
            slot->begin(), 
            slot->end(), 
            [&timer](const shared_ptr<TimeWheelData>& existingTimer) {
                return *existingTimer == *timer;
            }
        );
    
        if (it != slot->end()) {
            cout << "Timer already exists in the slot" << endl;
            return;
        }
    
        timer->isActive = true;
        timer->currentIndex = insertIndex;
        slot->push_front(std::move(timer));
    }
};

class TimeWheelSeconds : public TimeWheelCommonality{
public:
    TimeWheelSeconds() : TimeWheelCommonality(60, TimeWheelType::SECONDSWHEEL){}

    void AddTimer(shared_ptr<TimeWheelData> timer) override {
        if (!timer || (timer->clientData.lock() == nullptr)) {
            cerr << "This timer is invalid" << endl;
            return;
        }
    
        int64_t offset = timer->expireTime;
        if (offset < 0) {
            timer->CallBackFunction(timer->clientData.lock());
            return;
        }
    
        int insertIndex = currentSlotIndex_ + (static_cast<int>(offset) / SECONDS) % slotNumber_;
        cout << "Insert time wheel successfully" << endl;
    
        auto& slot = slots_[insertIndex];
        auto it = find_if(
            slot->begin(), 
            slot->end(), 
            [&timer](const shared_ptr<TimeWheelData>& existingTimer) {
                return *existingTimer == *timer;
            }
        );
    
        if (it != slot->end()) {
            cout << "Timer already exists in the slot" << endl;
            return;
        }
    
        timer->isActive = true;
        timer->currentIndex = insertIndex;
        slot->push_front(std::move(timer));
    }
};

class TimeWheelMilliSeconds : public TimeWheelCommonality{
public:
    TimeWheelMilliSeconds() : TimeWheelCommonality(50, TimeWheelType::MILLISECONDSWHEEL){}

    void AddTimer(shared_ptr<TimeWheelData> timer) override{
        if (!timer || (timer->clientData.lock() == nullptr)) {
            cerr << "This timer is invalid" << endl;
            return;
        }
    
        int64_t offset = timer->expireTime;
        if (offset < 0) {
            timer->CallBackFunction(timer->clientData.lock());
            return;
        }
    
        int insertIndex = currentSlotIndex_ + (static_cast<int>(offset) / MILLISECONDS) % slotNumber_;
        cout << "Insert time wheel successfully" << endl;
    
        auto& slot = slots_[insertIndex];
        auto it = find_if(
            slot->begin(), 
            slot->end(), 
            [&timer](const shared_ptr<TimeWheelData>& existingTimer) {
                return *existingTimer == *timer;
            }
        );
    
        if (it != slot->end()) {
            cout << "Timer already exists in the slot" << endl;
            return;
        }
    
        timer->isActive = true;
        timer->currentIndex = insertIndex;
        slot->push_front(std::move(timer));
    }
};

TimeWheel5.hpp

#include "TimeWheels.hpp"
#include <unistd.h>

typedef forward_list<shared_ptr<TimeWheelData>>::iterator slot_iterator_t;

class TimeWheel5 {
private:
    TimeWheelDaily dailyTimeWheel_;        // 天级时间轮
    TimeWheelHours hoursTimeWheel_;       // 小时级时间轮
    TimeWheelMinute minuteTimeWheel_;     // 分钟级时间轮
    TimeWheelSeconds secondsTimeWheel_;   // 秒级时间轮
    TimeWheelMilliSeconds millisecondsTimeWheel_;  // 毫秒级时间轮

    // 检查所有时间轮是否为空
    bool SlotsEmpty_();

    // 时间轮迁移函数
    template<class TimeWheel>
    void TickTimeWheel_(TimeWheel&, int64_t);

    // 迁移辅助函数
    template<class FromTimeWheel>
    void MigrateTimeWheel_(FromTimeWheel&, shared_ptr<TimeWheelData>&, int64_t&);
    
public:
    // 插入和删除定时器
    void InsertTimeWheel5(shared_ptr<TimeWheelData> timer);
    
    void DeleteTimeWheel5(shared_ptr<TimeWheelData> timer);
    
    // 驱动时间轮运转
    void RotateTimeWheel5();
};

// 检查所有时间轮是否为空
bool TimeWheel5::SlotsEmpty_() {
    return dailyTimeWheel_.IsEmpty() && 
           hoursTimeWheel_.IsEmpty() && 
           minuteTimeWheel_.IsEmpty() && 
           secondsTimeWheel_.IsEmpty() && 
           millisecondsTimeWheel_.IsEmpty();
}

// 插入定时器到合适的时间轮
void TimeWheel5::InsertTimeWheel5(shared_ptr<TimeWheelData> timer) {

    int64_t remainingTime = timer->expireTime;

    if(remainingTime >= MILLISECONDS && remainingTime < SECONDS) {
        millisecondsTimeWheel_.AddTimer(move(timer));
    }
    else if(remainingTime >= SECONDS && remainingTime < MINUTE) {
        secondsTimeWheel_.AddTimer(move(timer));
    }
    else if(remainingTime >= MINUTE && remainingTime < HOURS) {
        minuteTimeWheel_.AddTimer(move(timer));
    }
    else if(remainingTime >= HOURS && remainingTime < DAILY) {
        hoursTimeWheel_.AddTimer(move(timer));
    }
    else if(remainingTime >= DAILY && remainingTime < MONTH) {
        dailyTimeWheel_.AddTimer(move(timer));
    }
    else if(remainingTime > MONTH) {
        cout << "定时器超过30天,将被插入到天级时间轮(30天后到期)" << endl;
        timer->expireTime = MONTH;
        dailyTimeWheel_.AddTimer(move(timer));
    }
    else {
        cout << "定时器已到期,立即执行回调函数" << endl;
        timer->CallBackFunction(timer->clientData.lock());
    }
}

// 从时间轮删除定时器
void TimeWheel5::DeleteTimeWheel5(shared_ptr<TimeWheelData> timer) {
    int64_t remainingTime = timer->expireTime;

    if(remainingTime >= MILLISECONDS && remainingTime < SECONDS) {
        millisecondsTimeWheel_.RemarkTimerNotActive(timer);
    }
    else if(remainingTime >= SECONDS && remainingTime < MINUTE) {
        secondsTimeWheel_.RemarkTimerNotActive(timer);
    }
    else if(remainingTime >= MINUTE && remainingTime < HOURS) {
        minuteTimeWheel_.RemarkTimerNotActive(timer);
    }
    else if(remainingTime >= HOURS && remainingTime < DAILY) {
        hoursTimeWheel_.RemarkTimerNotActive(timer);
    }
    else if(remainingTime >= DAILY) {
        dailyTimeWheel_.RemarkTimerNotActive(timer);
    }
    else {
        cout << "定时器过期时间无效" << endl;
    }
}

// 驱动时间轮运转
void TimeWheel5::RotateTimeWheel5() {
	//如果全部的时间轮为空就停止转动
    while(!SlotsEmpty_()) {
    	/*
			判断每一级时间轮的是否转过一圈就要用上TimeWheelCommonality的RotateTimeWheel方法。
			当RotateTimeWheel方法为true的时候,就代表下级时间轮准了一圈,这时候就要上一级时间轮转一格
		*/
        bool millisecondsAdvance = millisecondsTimeWheel_.RotateTimeWheel();
        millisecondsTimeWheel_.TriggerTask();
        
        if(millisecondsAdvance) {
            bool secondsAdvance = secondsTimeWheel_.RotateTimeWheel();
            TickTimeWheel_<TimeWheelSeconds>(secondsTimeWheel_, (int64_t)SECONDS);
        
            if(secondsAdvance) {
                bool minuteAdvance = minuteTimeWheel_.RotateTimeWheel();
                TickTimeWheel_<TimeWheelMinute>(minuteTimeWheel_, (int64_t)MINUTE);

                if(minuteAdvance) {
                    bool hoursAdvance = hoursTimeWheel_.RotateTimeWheel();
                    TickTimeWheel_<TimeWheelHours>(hoursTimeWheel_, (int64_t)HOURS);
                    
                    if(hoursAdvance) {
                        bool dailyAdvance = dailyTimeWheel_.RotateTimeWheel();
                        TickTimeWheel_<TimeWheelDaily>(dailyTimeWheel_, (int64_t)DAILY);
                    }
                }
            }
        }

        usleep(20000);  // 20ms转动一次
    }
}

template<class TimeWheel>
void TimeWheel5::TickTimeWheel_(TimeWheel& timeWheel, int64_t timeUnit){
    
    if(timeWheel.GetSlots()[timeWheel.GetSlotCurrentIndex()] == nullptr || timeWheel.GetSlots()[timeWheel.GetSlotCurrentIndex()]->empty()){
        return;
    }
	//获取当前槽的迭代器
    slot_iterator_t prev = timeWheel.GetSlots()[timeWheel.GetSlotCurrentIndex()]->before_begin();
    slot_iterator_t iter = timeWheel.GetSlots()[timeWheel.GetSlotCurrentIndex()]->begin();
	//遍历当前槽链表,并触发/迁移任务
    while(iter != timeWheel.GetSlots()[timeWheel.GetSlotCurrentIndex()]->end()){
        shared_ptr<TimeWheelData> timer = *iter;
        int64_t remainingTime = timer->expireTime - timeWheel.GetSlotCurrentIndex() * timeUnit;
		//如果剩余时间大于0,就将定时器迁移到下级时间轮
        if(remainingTime > 0){
            MigrateTimeWheel_(timeWheel, timer, remainingTime);
            //迁移/触发后就要删掉存在于原链表的定时器
            iter = timeWheel.GetSlots()[timeWheel.GetSlotCurrentIndex()]->erase_after(prev);
        }
        //不是的话就触发
        else {
            if (timer->isActive && timer->CallBackFunction) {
                timer->CallBackFunction(timer->clientData.lock());
            }
            iter = timeWheel.GetSlots()[timeWheel.GetSlotCurrentIndex()]->erase_after(prev);
        }
    }

}

//迁移定时器的方法
template<class FromTimeWheel>
void TimeWheel5::MigrateTimeWheel_(FromTimeWheel& fromTimeWheel, shared_ptr<TimeWheelData>& timer, int64_t& remainingTime){
	//将timer复制到newTimer里
    shared_ptr<TimeWheelData> newTimer = make_shared<TimeWheelData>(*timer);
    //将剩余时间赋值给newTimer的expireTime内
    newTimer->expireTime = remainingTime;
    //重新插入
    InsertTimeWheel5(newTimer);
    //设定原迭代器为非活动
    fromTimeWheel.RemarkTimerNotActive(timer);
}

测试文件 TimerText.cpp

#include "TimeWheel5.hpp"
#include <iostream>
#include <memory>

using namespace std;

// 测试用的回调函数
void TestCallback(shared_ptr<ClientData> clientData) {
    if (clientData) {
        cout << "定时器触发! 客户端: " << clientData->clientName 
             << ", FD: " << clientData->fileDescriptor 
             << ", 消息: " << clientData->message << endl;
    } else {
        cout << "定时器触发,但客户端数据已失效" << endl;
    }
}

int main() {
    // 1. 创建五层时间轮实例
    TimeWheel5 timeWheel;
    
    // 2. 创建测试客户端数据
    auto client1 = make_shared<ClientData>();
    client1->clientName = "Client1";
    client1->fileDescriptor = 1001;
    client1->message = "心跳检测";
    
    auto client2 = make_shared<ClientData>();
    client2->clientName = "Client2"; 
    client2->fileDescriptor = 1002;
    client2->message = "会话超时";


    // 3. 创建定时器并设置不同到期时间
    auto timer1 = make_shared<TimeWheelData>();
    timer1->expireTime = 78980;  // 78.980秒后触发
    timer1->CallBackFunction = TestCallback;
    timer1->clientData = client1;
    
    auto timer2 = make_shared<TimeWheelData>();
    timer2->expireTime = 63098; // 65.0398秒后触发
    timer2->CallBackFunction = TestCallback;
    timer2->clientData = client2;

    // 4. 将定时器添加到时间轮
    cout << "添加定时器1 (78.980秒后触发)" << endl;
    timeWheel.InsertTimeWheel5(timer1);
    
    cout << "添加定时器2 (65.098秒后触发)" << endl;
    timeWheel.InsertTimeWheel5(timer2);

    //timeWheel.DeleteTimeWheel5(timer2);
    // 5. 启动时间轮运转
    cout << "时间轮开始运转..." << endl;
    timeWheel.RotateTimeWheel5();

    cout << "时间轮运行结束" << endl;

    return 0;
}

posted on 2025-07-26 09:29  楊思瞻  阅读(51)  评论(0)    收藏  举报

导航