功能结构代码逐步讲解
1、结构体定义
struct TimerNodeBase {
time_t expire; // 任务触发时间(单位:毫秒)
u_int64_t id; // 任务唯一ID
};
struct TimerNode : public TimerNodeBase {
using Callback = std::function<void(const TimerNode &node)>;
TimerNode(u_int64_t id, time_t expire, Callback func) : func(std::move(func)) {
this->expire = expire;
this->id = id;
}
Callback func; // 任务回调函数
};
TimerNodeBase 是任务的基础信息(触发时间+唯一ID)
TimerNode 是继承于TimerNodeBase,增加了回调函数Callback
2、定义比较器
bool operator < (const TimerNodeBase &lhd, const TimerNodeBase &rhd) {
if (lhd.expire < rhd.expire) return true;
else if (lhd.expire > rhd.expire) return false;
else return lhd.id < rhd.id;
}
按触发时间升序排序,时间相同时,先入先出
3、Timer类
3.1成员变量
static u_int64_t gid; // 全局唯一id生成器
std::set<TimerNode, std::less<>> timeouts; // 任务集合,按触发时间排序
3.2获取当前时间戳
static inline time_t GetTick() {
return chrono::duration_cast<chrono::milliseconds>(chrono::steady_clock::now().time_since_epoch()).count();
}
3.3 添加定时任务
TimerNodeBase AddTimeout(int mesc, TimerNode::Callback func) {
time_t expire = GetTick() + mesc; // 当前时间+延迟时间 = 任务触发时间
auto pairs = timeouts.emplace(GenGID(), expire, std::move(func));
return static_cast<TimerNodeBase>(*pairs.first);
}
计算触发时间
生成唯一ID,插入到timeouts集合
返回该任务信息
3.4 删除定时任务
void DelTimeout(TimerNodeBase &node) {
auto iter = timeouts.find(node);
if(iter != timeouts.end()) {
timeouts.erase(iter);
}
}
按key找到任务,删除
3.5更新timerfd的超时时间
void UpdateTimerfd(const int fd) {
struct timespec abstime;
auto iter = timeouts.begin();
if(iter != timeouts.end()) {
abstime.tv_sec = iter->expire / 1000; // 毫秒转换成秒
abstime.tv_nsec = (iter->expire % 1000) * 1000000; // 毫秒转换成纳秒
} else {
abstime.tv_sec = 0;
abstime.tv_nsec = 0;
}
struct itimerspec its = {
.it_interval = {}, // 不循环,只触发一次
.it_value = abstime // 下一次超时的绝对时间点
};
timerfd_settime(fd, TFD_TIMER_ABSTIME, &its, nullptr);
}
从任务集合里找到最近的触发时间,设置给timerfd
timerfd会在该时间触发文件描述符变为可读
3.6 处理触发的定时任务
void HandleTimer(time_t now) {
auto iter = timeouts.begin();
while (iter != timeouts.end() && iter->expire <= now) {
iter->func(*iter); // 执行回调
iter = timeouts.erase(iter); // 删除任务
}
}
按时间依次检查集合中所有已到期任务
执行对应回调并删除任务
4、主函数流程
int timerfd = timerfd_create(CLOCK_MONOTONIC, 0);
int epfd = epoll_create(1);
struct epoll_event ev = {.events = EPOLLIN | EPOLLET};
epoll_ctl(epfd, EPOLL_CTL_ADD, timerfd, &ev);
auto node = timer->AddTimeout(2000, [&](const TimerNode &node){
// 2秒后执行
});
timer->DelTimeout(node); // 删除刚刚添加的定时任务
timer->AddTimeout(3000, [&](const TimerNode &node){
// 3秒后执行
});
while (true) {
timer->UpdateTimerfd(timerfd); // 更新下一次触发时间
int n = epoll_wait(epfd, evs, 64, -1); // 阻塞等待事件
time_t now = Timer::GetTick();
timer->HandleTimer(now); // 执行所有到期任务
}
创建timerfd和epoll,将timerfd加入到epoll监听
添加一个2秒定时任务后立即删除
添加一个3秒定时任务
进入死循环,每次:
-
根据下一个最近任务更新timerfd超时
-
epoll_wait等待timerfd超时触发
-
触发后调用HandleTimer执行所有到期任务
总结
定时器实现主要通过数据结构+检测机制,其中数据结构需要使用红黑树(multimap最优),检测机制结合IO多路复用、epoll_wait等。