定时器的实现方案:红黑树、最小堆与时间轮

目录

引言

一.定时器的方方面面(定义)

    1.定时器的定义和应用

    2.定时器实现

    3.选择不同的数据结构(红黑树/最小堆/时间轮)

二.基于红黑树容器set实现一个定时器集合(代码)


引言

在计算机编程领域,定时器是一个非常重要的组件,它可以在指定的时间点触发特定的任务。常见的定时器实现方案有红黑树、最小堆和时间轮,每种方案都有其独特的优缺点和适用场景。本文将深入探讨这三种定时器实现方案,分析它们的原理、性能和适用场景。

一.定时器的方方面面(定义)

1.定时器的定义和应用

①.定时器定义

组织大量定时任务模块,项目底层基础模块。

②.定时器应用

解决延时处理任务问题,如客户端与服务端连接检测。

③.心跳检测:

包括keep alive和应用层发送心跳包,区别在于检测范围和网络通畅性。

④.游戏开发中的定时器:

技能冷却和倒计时,需要高性能定时器。

2.定时器实现

①.定时器组成:

数据结构和驱动方式。

②.数据结构:

按触发时间排序或按执行序组织。

③.驱动方式:

包括reactor网络模型、time fd和帧更新。

3.选择不同的数据结构(红黑树/最小堆/时间轮)

红黑树

①.红黑树定义:

平衡二叉搜索树,有序结构。

②.红黑树特征:

有序、平衡、黑节点高度一致。

③.红黑树操作:

增删改查,左旋转右旋转,重新着色。

④.红黑树在定时器中的应用:

组织定时任务,保持平衡。

最小堆

①.最小堆定义:

完全二叉树,满足父子关系。

②.最小堆特征:

最小值为根节点,父子节点大小关系约束。

③.最小堆操作:

添加节点和删除节点,时间复杂度为O(log n)。

④.最小堆在定时器中的应用:

快速找到最小值,驱动定时器运行。

时间轮

①.时间轮定义:

按时间指针移动触发任务的机制。

②.时间轮特征:

适用于多线程环境,优势明显。

③.时间轮操作:

添加任务、删除任务、移动时间指针。

④.时间轮在定时器中的应用:

处理海量定时任务。

时间轮本质上是一个环形的数组,每个数组元素代表一个时间间隔,例如1秒、1分、1时等。时间轮通过指针的旋转来管理和执行定时任务,每个槽通常使用双向链表来存储该时间点需要执行的任务,以便高效地添加、删除和遍历任务。

二.基于红黑树容器set实现一个定时器集合(代码)

#include <sys/epoll.h>
#include <sys/timerfd.h>
#include <time.h> // for timespec itimerspec
#include <unistd.h> // for close

#include <functional>
#include <chrono>
#include <set>
#include <memory>
#include <iostream>

using namespace std;

//(基类)父类
struct TimerNodeBase {
    time_t expire;
    uint64_t id; 
};

//(派生类)子类
struct TimerNode : public TimerNodeBase {
    using Callback = std::function<void(const TimerNode &node)>;
     // 定义一个类型别名 Callback,表示一个接收 const TimerNode& 参数并返回 void 的函数类型
    Callback func;
    // 成员变量:一个 Callback 类型的函数对象

    
    // 构造函数:接收三个参数,并初始化 TimerNode 的成员
    TimerNode(int64_t id, time_t expire, Callback func) : func(func) {
        this->expire = expire;
        this->id = id;
    }
    
};

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;
}

class Timer {
public:
    static inline time_t GetTick() {
        return chrono::duration_cast<chrono::milliseconds>(chrono::steady_clock::now().time_since_epoch()).count();
    }//返回当前时间(毫秒级) 

    TimerNodeBase AddTimer(int msec, TimerNode::Callback func) {
        time_t expire = GetTick() + msec;//它首先计算定时器的过期时间 expire,即当前时间加上 msec。
        if (timeouts.empty() || expire <= timeouts.crbegin()->expire) {//如果 timeouts 集合为空,或者新定时器的过期时间早于或等于当前最迟的定时器,它就将定时器添加到集合中。
            auto pairs = timeouts.emplace(GenID(), expire, std::move(func));
            return static_cast<TimerNodeBase>(*pairs.first);
        }
        auto ele = timeouts.emplace_hint(timeouts.crbegin().base(), GenID(), expire, std::move(func));
        return static_cast<TimerNodeBase>(*ele);
    }
    
    void DelTimer(TimerNodeBase &node) {
        auto iter = timeouts.find(node);
        if (iter != timeouts.end())
            timeouts.erase(iter);
    }

    //处理到期的定时器
    void HandleTimer(time_t now) {
        auto iter = timeouts.begin();
        while (iter != timeouts.end() && iter->expire <= now) {
            iter->func(*iter);
            iter = timeouts.erase(iter);
        }
    }

public:
    virtual void UpdateTimerfd(const int fd) {
        struct timespec abstime;
          // 定义一个 timespec 结构体,用来保存定时器的绝对时间

        auto iter = timeouts.begin(); // 获取当前最早的定时器(即过期时间最小的定时器)
        if (iter != timeouts.end()) {    // 如果 timeouts 集合中有定时器
            abstime.tv_sec = iter->expire / 1000;// 过期时间的秒部分
            abstime.tv_nsec = (iter->expire % 1000) * 1000000;// 过期时间的毫秒部分转为纳秒
        } else {// 如果没有定时器,设置 abstime 为 0,表示没有定时器触发
            abstime.tv_sec = 0;
            abstime.tv_nsec = 0;
        }

         // 设置 itimerspec 结构体,指定定时器的触发时间
        struct itimerspec its = {
            .it_interval = {},// 设定定时器不重复触发,因此它的间隔时间设为 0
            .it_value = abstime // 设置定时器触发的绝对时间
        };
        timerfd_settime(fd, TFD_TIMER_ABSTIME, &its, nullptr);
    // 使用 timerfd_settime 来设置定时器文件描述符的时间
    // fd: 定时器文件描述符
    // TFD_TIMER_ABSTIME: 使用绝对时间而不是相对时间
    // &its: 指向 itimerspec 结构体的指针,定义定时器的触发时间和间隔时间
    }

private:
    static inline uint64_t GenID() {
        return gid++;
    }
    static uint64_t gid; 

    set<TimerNode, std::less<>> timeouts;
};
uint64_t Timer::gid = 0;

int main() {
    int epfd = epoll_create(1);

    int timerfd = timerfd_create(CLOCK_MONOTONIC, 0);
    //timerfd 是 Linux 下的一种定时器机制,用于定时事件的通知。它通过文件描述符提供与定时器相关的操作。

    struct epoll_event ev = {.events=EPOLLIN | EPOLLET};
    epoll_ctl(epfd, EPOLL_CTL_ADD, timerfd, &ev);
    
    unique_ptr<Timer> timer = make_unique<Timer>();
    int i = 0;
    timer->AddTimer(1000, [&](const TimerNode &node) {
        cout << Timer::GetTick() << " node id:" << node.id << " revoked times:" << ++i << endl;
    });

    timer->AddTimer(1000, [&](const TimerNode &node) {
        cout << Timer::GetTick() << " node id:" << node.id << " revoked times:" << ++i << endl;
    });

    timer->AddTimer(3000, [&](const TimerNode &node) {
        cout << Timer::GetTick() << " node id:" << node.id << " revoked times:" << ++i << endl;
    });

    auto node = timer->AddTimer(2100, [&](const TimerNode &node) {
        cout << Timer::GetTick() << " node id:" << node.id << " revoked times:" << ++i << endl;
    });
    timer->DelTimer(node);

    cout << "now time:" << Timer::GetTick() << endl;

    struct epoll_event evs[64] = {0};

    while (true) {
        timer->UpdateTimerfd(timerfd);
        int n = epoll_wait(epfd, evs, 64, -1);
        time_t now = Timer::GetTick();
        for (int i = 0; i < n; i++) {
            // for network event handle
        }
        timer->HandleTimer(now);
        //执行完事件后进行清理
    }
    epoll_ctl(epfd, EPOLL_CTL_DEL, timerfd, &ev);
    close(timerfd);
    close(epfd);
    return 0;
}

①基类 TimerNodeBase

time_t expire :定时器的过期时间 = 当前时间+设定时间

id:定时器模块的id

②子类 TimerNode 以public的方式继承基类

在原本基类里面增加唠嗑一个回调函数Callbaakc func

③小于号运算符重载(应用于红黑树实现从小到大排序)

④定义一个Timer类管理定时器的接口

GenID:获取id编号的私有接口

set<TimerNode,std::less<>>timeouts:节点类型为TimerNode 中序遍历按从小到大排序的一颗红黑树

⑤获取当前(毫秒级)时间的成员函数

⑥增加定时器模块的成员函数

如果 timeouts 集合为空,或者新定时器的过期时间早于或等于当前最迟的定时器,它就将定时器添加到集合中。

如果新定时器的过期时间一直大于当前最迟的定时器 则一直把它添加在红黑树的最右边

⑦删除指定的定时器模块

⑧处理过期的定时器模块 

func 回调函数的作用就是让定时器到期时,能够自定义处理逻辑(如打印日志、执行其他操作等

//传入时间之后,进行判断,小于这个时间的全部进行执行

//将这个删除之后会赋值一下下一个迭代

⑨更新timefd文件描述符

⑩把timerfd加入到epoll的监听集合中

⑩①调用AddTimer函数加入几个定时器模块

⑩②时间到了处理事件重新设定超时时间

⑩③清理工作

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值