时间轮定时器-Timewheel

最近有个大牛给我教了时间轮算法,并且看了他实现的源码,认为自己已经理解了他所实现的思想,这里将我的代码和理解分享一下:
时间轮是模仿钟表的方式,秒针走一圈,分针走一格,只有最里圈的任务需要执行,转一圈后分针(外一层)指向下一格,将那一格的节点重新插入,那么就会插入到秒针(最里圈)。
这里写图片描述
以下方式还有一些不足,我会再做修改:

#ifndef __TIMEWHEEL_H__
#define __TIMEWHEEL_H__
#include "templock.h"//锁的实现文件
#include <time.h>
#include <map>
#include <list>
#define SLOT_LEVEL 4 //时间圈的层数
#define TIME_NEAR  8 
#define TIME_LEVEL  6
#define TIME_NEAR_SLOT  (1 << TIME_NEAR) //最里圈执行圈的槽数 1 0000 0000
#define TIME_LEVEL_SLOT  (1 << TIME_LEVEL) //除最里圈外的每圈的桶数 0100 0000
#define TIME_NEAR_MASK  ((1 << TIME_NEAR) - 1) //判断任务是否移动到最里圈的掩码 1111 1111
#define TIME_LEVEL_MASK  ((1 << TIME_LEVEL) - 1) //判断任务放在哪一圈的掩码 0011 1111
// 精度
#define PRECISION  10



typedef void(*TimeOutCb)(void*);
typedef void(*CancelCb)(void*);
typedef struct  TimerNode
{
    unsigned int expireTime;//过期时间
    TimeOutCb timeOutCb; // 时间节点的回调处理函数
    CancelCb cancelCb; // 取消删除时间节点
    void* arg;  // 回调函数的参数
    int timeNodeId;//定时任务节点的ID,用来删除节点
    bool isNear; // 是否处在最近
    int rowNO; // 行,所处圈数
    int colNO;// 列,所在位置
};
class TimeWheel
{

public:
    TimeWheel(void);
public:
    ~TimeWheel(void);
private:
    std::list<TimerNode*> nearTimerSlot[TIME_NEAR_SLOT]; //最里面的时间轮槽数,总共256个
    std::list<TimerNode*> levelTimerSlot[SLOT_LEVEL][TIME_LEVEL_SLOT];  // 后面总共4层,每层都是64个槽
    unsigned int tick;      // 当前的tick
    unsigned int startTime; // 开始时间
    unsigned int currentTime;   //当前时间
private:
    TempLock timeLock;
    int timeNodeId; // 时间点的ID号
    std::map<int, TimerNode*> timeNodeMap; //定时任务节点ID和地址的映射
    volatile bool stopFlag; //是否停止
public:
    int  setTimer(unsigned int  milliSeconds, TimeOutCb cb, void* arg, CancelCb cancelCb = NULL);
    void cancelTimer(int timeNodeId);
private:
    int  setTimerInnerSafe(unsigned int  milliSeconds, TimeOutCb cb, void* arg, CancelCb cancelCb);
    void cancelTimerInnerSafe(int timeNodeId);
    void startTimer();
    void stopTimer(); // 停止
    void updateTimer();
    void deleteNode(TimerNode* timeNode, bool isTimeOut = true);
    void executeExpireTimeNodeSafe();
    void InsertTimeNode(TimerNode* newTimeNode);
    void moveTimeList();
    void moveTimeNodeSafe(std::list<TimerNode*>& nodeList);
    TimerNode* createTimeNode(unsigned int  milliSeconds, TimeOutCb timeOutCb, void* arg, CancelCb cancelCb);

private:
    static void runTimer(void* arg);
private:
    static int gettimeofday(struct timeval* tv, struct timezone* tz);
    static void InitHighResAbsoluteTime();
    static long getCentiSeconds();
};

#endif
#pragma once
#include <windows.h>
#include <process.h>
#include "TimeWheel.h"
#include <stdio.h>

#define DELTA_EPOCH_IN_MICROSECS  11644473600000000Ui64
static VOID(WINAPI *fnGetSystemTimePreciseAsFileTime)(LPFILETIME) = NULL;
struct timezone {
    int  tz_minuteswest; /* minutes W of Greenwich */
    int  tz_dsttime;     /* type of dst correction */
};

TimeWheel::TimeWheel(void)
    :stopFlag(false) 
{
    startTimer();//构造时就开始轮转时间
}


TimeWheel::~TimeWheel(void)
{
    stopFlag = true;
}


void TimeWheel::runTimer(void* arg){
    TimeWheel* timer = (TimeWheel*)arg;
    timer->updateTimer();
}
// 初始化所有的数组
void  TimeWheel::startTimer(){
    timeNodeId = 0;
    tick = 0;
    startTime = getCentiSeconds();// 记录开始时间
    currentTime = startTime; // 记录当前时间
    _beginthread(runTimer,0,this); //开启一个线程来执行任务检测和执行
}
void TimeWheel::stopTimer() { // 停止
    stopFlag = true;
}
void TimeWheel::deleteNode(TimerNode* timeNode,bool isTimeOut){
    if (timeNode->isNear)//判断是否在要执行的圈
    {
        nearTimerSlot[timeNode->colNO].remove(timeNode);//对应槽的队列删除这个节点
    } else {
        levelTimerSlot[timeNode->rowNO][timeNode->colNO].remove(timeNode);//对应层的对应槽删除这个节点
    }
    timeNodeMap.erase(timeNode->timeNodeId); // 从map中删除这个映射
    delete timeNode;    
}

void TimeWheel::executeExpireTimeNodeSafe() {
    timeLock.lock();
    unsigned int triggeSlot = tick & TIME_NEAR_MASK;//判断当前时间在哪个槽,&11111111
    std::list<TimerNode*> nodeList = nearTimerSlot[triggeSlot];//取出槽上挂的任务列表
     nearTimerSlot[triggeSlot].clear();//然后清空该槽上的任务列表,定时任务只执行一次
    timeLock.unlock();

    for (auto it = nodeList.begin(); it != nodeList.end();++it)//遍历任务列表
    {
        TimerNode* curNode = *it;
        curNode->timeOutCb(curNode->arg); // 调用回调函数,这里监控的问题
        timeLock.lock();
        timeNodeMap.erase(curNode->timeNodeId); // 删除Map中的世界节点,执行过后就不需要记录了
        timeLock.unlock();
        delete curNode;//删除该时间节点
    }
}
void TimeWheel::moveTimeNodeSafe(std::list<TimerNode*>& nodeList ){
    timeLock.lock();
    for (auto it = nodeList.begin(); it != nodeList.end();)//遍历槽上的定时任务,重新插入到时间轮中
    {
        TimerNode*  moveNode = *it;
        InsertTimeNode(moveNode); // 重新插入
        it = nodeList.erase(it++);//将原位置的任务删掉
    }
    timeLock.unlock();
}
void TimeWheel::moveTimeList(){
    unsigned int curTick = ++tick;//当前时间等于tick+1
    if (curTick == 0)
    {
        return;
    }
    unsigned int mask = 1 << TIME_NEAR; //1 0000 0000
    unsigned int time = curTick >> TIME_NEAR; // 时间右移8位,
    // 等于0 表示进位
    int i = 0;
    //如果当前时间&上掩码等于0, 那么轮转弯1圈了,需要移动任务列表
    while((curTick & (mask - 1)/*1111 1111*/) == 0) {       
        int moveSlot = time & TIME_LEVEL_MASK; //时间&0011 1111,移动的槽的位置
        if (moveSlot != 0) // 最小为1,
        {
            moveTimeNodeSafe(levelTimerSlot[i][moveSlot]);
            levelTimerSlot[i][moveSlot].clear();
            break;
        }
        time >>= TIME_LEVEL;//时间右移6位,判断下一层
        mask <<= TIME_LEVEL;//掩码左移6位,判断是哪一圈
        ++i;//层数加1
    }
}
void TimeWheel::updateTimer(){
    while (!stopFlag)//判断该定时器是否结束生命周期
    {
        int curTime = getCentiSeconds();//获取当前时间
        int timeVal = curTime - currentTime;//获取当前时间和刚才的时间差
        currentTime = curTime;//然后记录当前时间
        for (int i = 0; i < timeVal; i++) //循环时间差的次数
        {
            executeExpireTimeNodeSafe();//先执行一次,检查0位槽是否有要执行的任务
            moveTimeList();//移动时间队列
            executeExpireTimeNodeSafe();//执行槽上的任务
        }
        Sleep(PRECISION);//沉睡10ms,精度差异
    }
}
//初始化时间节点
TimerNode* TimeWheel::createTimeNode(unsigned int  milliSeconds,TimeOutCb timeOutCb,void* arg,CancelCb cancelCb){
    TimerNode* newTimeNode = new TimerNode;
    memset(newTimeNode,0,sizeof(TimerNode));
    newTimeNode->arg = arg;
    newTimeNode->expireTime = tick + milliSeconds;
    newTimeNode->timeOutCb = timeOutCb;
    newTimeNode->cancelCb = cancelCb;
    return newTimeNode;
}
//主要使用的方法,用来执行定时任务
int  TimeWheel::setTimer(unsigned int  milliSeconds,TimeOutCb cb,void* arg,CancelCb cancelCb){
    return setTimerInnerSafe(milliSeconds,cb,arg,cancelCb);
}
//根据定时任务的ID从列表和map中删除他
void  TimeWheel::cancelTimer(int timeNodeId) {
    cancelTimerInnerSafe(timeNodeId);
}
int  TimeWheel::setTimerInnerSafe(unsigned int  milliSeconds,TimeOutCb cb,void* arg,CancelCb cancelCb) {
    timeLock.lock();
    //printf("设置----\n");
    milliSeconds /= PRECISION; // 精度
    TimerNode*  newTimeNode = createTimeNode(milliSeconds,cb,arg,cancelCb);
    newTimeNode->timeNodeId = ++timeNodeId;
    timeNodeMap[newTimeNode->timeNodeId] = newTimeNode; // 放到map里面
    InsertTimeNode(newTimeNode);//插入新的时间节点
    timeLock.unlock();
    return newTimeNode->timeNodeId;
}
void TimeWheel::cancelTimerInnerSafe(int timeNodeId) {
    timeLock.lock();    
    TimerNode* timeNode = timeNodeMap[timeNodeId];//根据ID查找到节点地址
    if (timeNode == NULL)//如果节点不存在,那么就返回
    {
        timeLock.unlock();
        return;
    }
    // 执行这个节点取消的回调
    if (timeNode->cancelCb != NULL)
    {
        timeNode->cancelCb(timeNode->arg);  
    }
    timeNodeMap.erase(timeNodeId);//map中删除这个映射
    deleteNode(timeNode);//删除这个节点
    timeLock.unlock();
}
void  TimeWheel::InsertTimeNode(TimerNode* newTimeNode){
    unsigned int expireTime = newTimeNode->expireTime; //到期时间
    unsigned int milliSeconds = newTimeNode->expireTime - tick; // 剩余多少时间的Tick
    if ((milliSeconds | TIME_NEAR_MASK) == TIME_NEAR_MASK)//判断是否已经到快要执行的时间了
    {
        int insertSlot = expireTime & TIME_NEAR_MASK; //计算插入到最里圈255个槽的哪个槽
        newTimeNode->isNear = true; //将即将执行设为1
        newTimeNode->colNO = insertSlot; //列设为槽数
        nearTimerSlot[insertSlot].push_back(newTimeNode);//放入即将执行的位置
    } else {//如果不是即将执行的,不在255个槽的范围内,那么就要判断放在哪个位置
        unsigned int mask = (1 << TIME_NEAR) << TIME_LEVEL; //掩码是 100 0000 0000 0000,后8位是最里圈的范围,之前的是层数范围
        int i = 0;
        for (i = 0; i < SLOT_LEVEL; i++) //不会超过最大层数
        {
            if ((milliSeconds | (mask - 1))  == (mask - 1)) //如果当前时间或上11 1111 1111 1111还等于11 1111 1111 1111的话,那么它就在这个范围内,就是这层没错
            {
                break;
            }
            mask = mask << TIME_LEVEL;//掩码左移6位,就是下一层,然后再判断是否在下一层的位置
        }
        int insertSlot = (expireTime >> (TIME_NEAR + i * TIME_LEVEL)) & (mask - 1); // 计算这一层应该插入哪个槽
        newTimeNode->isNear = false;//不在执行圈
        newTimeNode->rowNO = i;//层数
        newTimeNode->colNO = insertSlot;//槽数
        levelTimerSlot[i][insertSlot].push_back(newTimeNode);//插到应该的位置
    }
}
// 1秒 = 1000 毫秒 = 1000,000 微秒
// 获取百分之一秒
long TimeWheel::getCentiSeconds() {
    struct timeval tv;
    gettimeofday(&tv,NULL);
    return tv.tv_sec * 1000 / PRECISION  + tv.tv_usec / (1000 * PRECISION); 
}


//****************************工具函数*******************************
int  TimeWheel::gettimeofday(struct timeval* tv,struct timezone* tz){
    FILETIME ft;
    unsigned __int64 tmpres = 0;
    static int tzflag;

    if (NULL == fnGetSystemTimePreciseAsFileTime) {
        InitHighResAbsoluteTime();
    }
    if (NULL != tv) {
        fnGetSystemTimePreciseAsFileTime(&ft);

        tmpres |= ft.dwHighDateTime;
        tmpres <<= 32;
        tmpres |= ft.dwLowDateTime;
        /*converting file time to unix epoch*/
        tmpres /= 10;  /*convert into microseconds*/
        tmpres -= DELTA_EPOCH_IN_MICROSECS;
        tv->tv_sec = (long) (tmpres / 1000000UL);
        tv->tv_usec = (long) (tmpres % 1000000UL);
    }
    if (NULL != tz) {
        if (!tzflag) {
            _tzset();
            tzflag++;
        }
        tz->tz_minuteswest = _timezone / 60;
        tz->tz_dsttime = _daylight;
    }
    return 0;
}

void  TimeWheel::InitHighResAbsoluteTime() {
    FARPROC fp;
    HMODULE module;
    if (fnGetSystemTimePreciseAsFileTime != NULL)
        return;
    fnGetSystemTimePreciseAsFileTime = GetSystemTimeAsFileTime;
    module = GetModuleHandleA("kernel32.dll");
    if (module) {
        fp = GetProcAddress(module, "GetSystemTimePreciseAsFileTime");
        if (fp) {
            fnGetSystemTimePreciseAsFileTime = (VOID(WINAPI*)(LPFILETIME)) fp;
        }
    }
}

使用方式:

TimeWheel tiemer;
timer.settime(300,CB,void*arg,cancelCB);
//然后就等待执行拉
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值