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