手写一个高性能定时器

功能结构代码逐步讲解

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等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值