【C/C++】五层时间轮的设计与实现
1.概述
网络程序需要处理的一种事件就是定时事件,比如定期检测一个客户端的连接活动状态。服务器程序通常需要管理多个定时任务,因此有效地组织这些定时事件,使之能在预期的时间内被触发且不影响服务器的主要实现,对于服务器的性能有着很重要的影响,为此我们要将每个定时任务封装成定时器。
封装定时器可以使用很多数据结构,链表,排序链表,红黑树,堆,时间轮都可以。本文主要是讲述一个较为高效的定时器容器——时间轮。
2.原理
2.1.单层时间轮示意
时间轮算法借鉴了生活中时钟的例子,并依据Hash表的思想,将定时器散列到不同的链表上,如下图所示,时间轮内有着许多的槽,每一个槽都是一个链表,在设立触发事件的时间并插入到时间轮时,程序会根据设定的时间和当前指针的位置来看插入到哪个槽内。数学公式如下:
其中,ts是插入的槽的位置,si是当前指针转动一步的时间间隔,ti是定时器的时间,N是槽总数。
在确定插入的槽的位置时,定时任务会被插入到该槽内的链表中。删除也是同理,根据定时任务的时间确定其所在槽的位置,并删除其所在链表内的定时器。
图中有一个指针,这个指针要以恒定的速度顺时针转动,每转动一步就要指向下一个槽,每次转动都称为一个Tick,当指针转动到有task的槽的时候,程序将会遍历这个槽的链表并将链表内的定时任务迁移/触发。
很显然,对于时间轮而言,要提高精度,就得使得si足够小;要使得时间轮的执行效率足够大,就要保证N的值足够大。

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

当上一级时间轮的指针指向了含有定时器的槽的时候,程序会将该槽的定时器迁移到下一层时间轮内,具体步骤如下:
- 将原定时任务的时间减去该槽的index和 tt 的乘积(tt指的是该时间轮一个单位的量和最底层时间轮一个单位的量的商,比如有三个时间轮,小时轮,秒轮和毫秒轮,那么小时轮的tt就是360000,秒轮的tt就是1000)。
- 按照剩余时间的大小判断要插入到哪个时间轮,哪个槽内。
这样就可以大大增加精度和执行效率,同时空间也会节省很多。(上面那个时间轮要用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类图

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的插入逻辑是一样的,大抵分为以下几个步骤(以毫秒轮为例):
- 计算该插入的槽
int insertIndex = currentSlotIndex_ + (static_cast<int>(offset) / MILLISECONDS) % slotNumber_;
- 查看该槽是否存在过相同的定时任务
auto& slot = slots_[insertIndex];
/*泛型算法,寻找槽里是否有相同的TimeWheelData对象*/
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));
代码会在最后展示,每个轮的逻辑都是一样的。
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.时间轮的迁移
我们在插入多层时间轮的时候,是按照它的超时时间来确定插入的时间轮的,随着时间轮的运转,当定时器剩余时间小于该时间轮的单位时间时,就要启动迁移程序,朝着下一级时间轮迁移,具体如

首先我们要做一个函数,能做到将定时任务从一个时间轮迁移到另一个时间轮,本程序使用了一个方法模板,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;
}
浙公网安备 33010602011771号