在实际工作中我们不能为每一个定时任务创建一个定时器,因为定时器也是一种珍贵的资源,在linux中可以使用alarm信号,timerfd,epoll_wait,select都可以来实现定时器。其中epoll_wait和select提供了延时作用,alarm采用异步的信号,timerfd系列需要在select/epoll中注册定时器事件EPOLLIN,并在事件发生后uint64_t x; read(timerfd,&x,sizeof(x));否则select和epoll的水平触发模式下会不断的提醒定时器事件。
时间轮算法可用来管理大量的定时事件,根据设置的定时时间与当前时间轮所在的位置(表盘上指针所指的位置),计算出这个定时事件应该被放入哪个桶中(时间轮上有多个tick,每个tick代表一个桶,桶可以是一个list,装着很多事件)。
定时溢出,时间轮算法还存在一个问题,就是它算表示的时间是用范围的,比如一个时间轮有60个tick,每个tick为1秒,那么如果想要定时70秒的定时事件将无法加入到时间轮中,当然可以增加tick的数量,比如将它增大的1000个,但是这样也只能表示1000秒范围内的定时任务,而且1000个tick也需要占用很多内存空间。
分级时间轮,将时间轮分级,就像钟表上的时分秒一样,当秒针到60的时候分钟才变动一下,当分钟到60时时针才变动一下。这样可增大表示范围,而且并不需要太多tick空间。当然每个tick的时长,tick的数量都是可以自定义的,在添加定时事件时,计算出定时任务触发时各个分级时间轮应在的位置(就像定时100秒,然后根据当前时间 + 100再转换成时分秒的位置),定时任务只有在时针对应时才去考虑分针,在分针对应时才考虑秒针,当秒针对应时说明定时时间已到,执行任务。
参考文章(里面有图,我就不画了):https://blog.csdn.net/xinzhongtianxia/article/details/86221241
下面代码:(我利用epoll_wait延时1秒,制作时分秒三个时间轮)
MyTimeWheel.h
#pragma once
/*
分层时间轮:时分秒
*/
#include <vector>
#include <unordered_set>
#include <unordered_map>
#include <list>
#include <functional>
#include <memory>
#include <thread>
#include <mutex>
#include <condition_variable>
using namespace std;
enum TIME{SECOND = 1,MINUTE = 60,HOUR = 3600};
typedef function<void()> TIMER_CALLBACK;
struct WheelPos {
int hour;
int minute;
int second;
WheelPos(int h,int m,int s) :hour(h), minute(m), second(s) {}
};
class MyTimerWheel;
struct timer_item {
private:
bool valid;
WheelPos wheelPos;
int seconds; //秒
TIMER_CALLBACK func;
bool is_round;
public:
friend class MyTimerWheel;
timer_item(WheelPos pos,int tmout, bool isRound,TIMER_CALLBACK callFunc = nullptr):
valid(true), wheelPos(pos),
seconds(tmout),func(callFunc), is_round(isRound){
}
};
class MyTimerWheel
{
public:
MyTimerWheel();
~MyTimerWheel();
public:
//失败返回-1
shared_ptr<timer_item> addTimer(int seconds,bool is_round = false, TIMER_CALLBACK func = nullptr);
void delTimer(shared_ptr<timer_item> handle);
private:
static void thread_proc(void* pvoid);
WheelPos getWheelPos(int seconds);
private:
enum {HOUR_TICK = 24,MINUTE_TICK = 60,SECOND_TICK = 60};
unordered_map<int, list<shared_ptr<timer_item>>> hourWheel;
unordered_map<int, list<shared_ptr<timer_item>>> minuteWheel;
unordered_map<int, list<shared_ptr<timer_item>>> secondWheel;
volatile int hourCurPos,minuteCurPos,secondCurPos;
int epollFd;
thread* t;
bool shutdown;
mutex mutex_;
};
MyTimeWheel.cpp
#include "MyTimerWheel.h"
#include <sys/epoll.h>
#include <unistd.h>
MyTimerWheel::MyTimerWheel():hourCurPos(0), minuteCurPos(0), secondCurPos(0),
epollFd(0), shutdown(false){
epollFd = epoll_create(5);
t = new thread(bind(thread_proc, (void*)this));
}
MyTimerWheel::~MyTimerWheel()
{
shutdown = true;
t->join();
delete t;
close(epollFd);
}
shared_ptr<timer_item> MyTimerWheel::addTimer(int seconds, bool is_round, TIMER_CALLBACK func) {
if (seconds <= 0) {
return nullptr;
}
auto wheelPos = getWheelPos(seconds);
shared_ptr<timer_item> timerptr(new timer_item(wheelPos, seconds, is_round, func));
lock_guard<mutex> lock(mutex_);
if (wheelPos.hour >= 0) {
hourWheel[wheelPos.hour].push_back(timerptr);
}
else if (wheelPos.minute >= 0) {
minuteWheel[wheelPos.minute].push_back(timerptr);
}
else if(wheelPos.second >= 0) {
secondWheel[wheelPos.second].push_back(timerptr);
}
return timerptr;
}
void MyTimerWheel::delTimer(shared_ptr<timer_item> handle) {
auto wheelPos = handle->wheelPos;
lock_guard<mutex> lock(mutex_);
if (wheelPos.hour >= 0) {
hourWheel[wheelPos.hour].remove(handle);
}
else if (wheelPos.minute >= 0) {
minuteWheel[wheelPos.minute].remove(handle);
}
else if (wheelPos.second >= 0) {
secondWheel[wheelPos.second].remove(handle);
}
}
WheelPos MyTimerWheel::getWheelPos(int seconds) {
int h = -1, m = -1, s = -1;
if (seconds >= HOUR * 1) {
int tmp = seconds / (HOUR * 1);
h = (tmp + hourCurPos) % HOUR_TICK;
seconds -= (tmp * HOUR * 1);
}
if (seconds >= MINUTE * 1) {
int tmp = seconds / (MINUTE * 1);
m = (tmp + minuteCurPos) % MINUTE_TICK;
seconds -= (tmp * MINUTE * 1);
}
s = (secondCurPos + seconds) % SECOND_TICK;
return WheelPos(h, m, s);
}
void MyTimerWheel::thread_proc(void* pvoid) {
MyTimerWheel* mtw = (MyTimerWheel*)pvoid;
struct epoll_event events[5];
while (!mtw->shutdown) {
int ret = epoll_wait(mtw->epollFd, events, 1024, 1000);
if (ret == 0) {
lock_guard<mutex> lock(mtw->mutex_);
++mtw->secondCurPos;
if (mtw->secondCurPos == SECOND_TICK) {
mtw->secondCurPos = 0;
++mtw->minuteCurPos;
for (auto x : mtw->minuteWheel[(int)mtw->minuteCurPos]) {
if (x->valid) {
mtw->secondWheel[x->wheelPos.second].push_back(x);
}
}
mtw->minuteWheel[(int)mtw->minuteCurPos].clear();
}
if (mtw->minuteCurPos == MINUTE_TICK) {
mtw->minuteCurPos = 0;
++mtw->hourCurPos;
for (auto x : mtw->hourWheel[(int)mtw->hourCurPos]) {
if (x->valid) {
mtw->minuteWheel[x->wheelPos.minute].push_back(x);
}
}
mtw->hourWheel[(int)mtw->hourCurPos].clear();
}
if (mtw->hourCurPos == HOUR_TICK) {
mtw->hourCurPos = 0;
}
for (auto x : mtw->secondWheel[(int)mtw->secondCurPos]) {
if (x->valid) {
x->func();
}
if (!x->is_round) {
x->valid = false;
}
else {
auto wheelPos = mtw->getWheelPos(x->seconds);
x->wheelPos = wheelPos;
if (wheelPos.hour >= 0) {
mtw->hourWheel[wheelPos.hour].push_back(x);
}
else if (wheelPos.minute >= 0) {
mtw->minuteWheel[wheelPos.minute].push_back(x);
}
else if (wheelPos.second >= 0) {
mtw->secondWheel[wheelPos.second].push_back(x);
}
}
}
mtw->secondWheel[(int)mtw->secondCurPos].clear();
}
else if (ret == -1) {
if (errno == EINTR || errno == EAGAIN) {
continue;
}
perror("epoll error:");
abort();
}
}
}