时间轮定时器

引言

本文章参考《Linux高性能服务器编程》,本篇文章还是主要是以代码为主,相比于上篇文章的升序链表定时器,我们的时间轮定时器性能会有很大的提升。完整的代码我已经上传到了github上:

fengyue05/Linux-: 本仓库里面会有关于《Linux高性能服务器编程》的一些主要代码实现

简介

时间轮的构成主要就是链表,槽slot,还有每一个槽之间的时间间隔si,每一次转动时一个tick。我们每一个槽里面都有一个链表,然后每一个链表里面的每一个节点都是一个定时器,那定时器的终止时间的差值都是N个转动周期。时间轮使用的是哈希思想,就是把定时器分散到不同的链表上面,所以对于时间轮而言,精度越高那么si越高,执行效率月高,那么N越大,因为链表上面的定时器就越少。

我们这个链表里面的定时器没有任何的大小关系

代码

对于一个定时器,因为处于时间轮里面,所以必须记录其处于的槽位,还有轮转数,因为一个轮盘就那么多槽位,如果转的圈数比较的多,那么就会有很多圈。

cpp 复制代码
class TwTimer {
public:
    using Clock = std::chrono::steady_clock;
    using TimeOut = Clock::time_point;
    using Callback = std::function<void(ClientData*)>;

    TwTimer(int rot, int ts, ClientData* user);
    friend class TimeWheel;
private:
    int rotation; // 轮转数,因为一个轮盘就那么多槽位,如果转的圈数比较的多,那么就会有很多圈,这也是需要记录的
    int timeSlot; // 所属槽位
    
    Callback callback;
    ClientData* userData;

    std::shared_ptr<TwTimer> m_next;
    std::weak_ptr<TwTimer> m_prev;
};

对于这个时间轮,有一些对应的操作函数,然后用一个数组存储所有的槽位,并记录当前的结点。

cpp 复制代码
class TimeWheel {
public:
    static constexpr int N = 60; //  时间轮的槽数
    static constexpr int SI = 1; // 每秒转动1次

    using TimePtr = std::shared_ptr<TwTimer>;

    TimeWheel();
    ~TimeWheel() = default;

    TimePtr addTimer(int timeout, ClientData* userData);

    void delTimer(TimePtr timer);

    void tick();

private:
    std::array<TimePtr, N> slots;
    int curSlot;
};

这是两个构造函数,第一个定时器的构造函数就说明在一开始就已经确定了其轮转的圈数,槽位,还有所属的客户数据(互相绑定)

cpp 复制代码
TwTimer::TwTimer(int rot, int ts, ClientData *user) 
        : rotation(rot)
        , timeSlot(ts)
        , userData(user)
{

}

TimeWheel::TimeWheel() : curSlot(0)
{   
    for (auto& slot : slots) {
        slot.reset();
    }
}

添加定时器,我们根据超时的事件确定转到此处需要多少个tick,然后确定其转的圈数,槽位,最后直接用头插法插入链表。

cpp 复制代码
TimeWheel::TimePtr TimeWheel::addTimer(int timeout, ClientData *userData)
{
    if (timeout < 0) {
        return nullptr;
    }
    int ticks = 0;
     /*
        一个tick代表SI秒
        timeout=10
        ticks=10
    */
   ticks = timeout / SI;
   if (ticks < 1) {
        ticks = 1;
   }
   int rotation = ticks / N;
   int ts = (curSlot + ticks) % N;
   auto timer = std::make_shared<TwTimer>(rotation, ts, userData);
   if (slots[ts]) {
        slots[ts]->m_prev = timer;
        timer->m_next = slots[ts];
   }
   slots[ts] = timer;
   return timer;
}

删除定时器,确认其定时器的槽位,也就是在数组里面的位置,并且对于一个定时器,我们知道其前后的定时器,所以根本不需要遍历这个链表,这也是我们提高效率的一个地方,我们直接得到前后的两个定时器来操作。

cpp 复制代码
void TimeWheel::delTimer(TimePtr timer)
{
    if (!timer) {
        return;
    }
    int ts = timer->timeSlot;
    auto prev = timer->m_prev.lock();
    auto next = timer->m_next;

    if (!prev) {
        slots[ts] = next;
        if (next) {
            next->m_prev.reset();
        }
    }
    else {
        prev->m_next = next;
        if (next) {
            next->m_prev = prev;
        }
    }

    timer->m_next.reset();
    timer->m_prev.reset();
}

这个函数就是每一次转动之后的操作,也就是取得转动之后当前的槽,然后一个一个遍历当前链表的定时器,如果有超时的就执行回调函数然后删除,如果没有就下一个。

这也就是为什么N越大性能越好,因为一个槽里面的一个链表中定时器如果越来越多,那么性能肯定是越来越低,因为里面的定时器没有大小关系,所以我们必须一个一个的遍历。

cpp 复制代码
void TimeWheel::tick()
{
    auto current = slots[curSlot];
    while(current) {
        auto next = current->m_next;
        if (current->rotation > 0) {
            current->rotation--;
        }
        else {
            //  超时
            if (current->callback) {
                current->callback(current->userData);
            }
            delTimer(current);
        }
        current = next;
    }
    curSlot++;
    if (curSlot >= N) {
        curSlot = 0;
    }
}

总结

本篇文章到这里就结束了!!!希望可以帮助大家理解~~~~

相关推荐
小祺先生6 小时前
mt7921 debian系统 如何安装驱动
运维·网络·debian
笨笨饿6 小时前
#138_解决Codex要五次回复的问题
linux·stm32·单片机·嵌入式硬件·mcu·物联网·嵌入式实时数据库
m0_459872926 小时前
Linux 系统基础
linux·运维·服务器
夜不会漫长6 小时前
C++:类和对象(2)
java·javascript·c++
吴声子夜歌6 小时前
Shell编程——数组
linux·运维·shell
无小道6 小时前
C++ 中 `explicit` 关键字详解:彻底理解隐式类型转换
c++·explicit
蒸蒸yyyyzwd7 小时前
cpp 选手秋招学习笔记 day34
c++·八股
Tairitsu_H7 小时前
[C++] C++11类和STL的新特性
开发语言·c++·stl·c++11
qq_508576097 小时前
机器人通讯协议
网络·机器人
2401_891957317 小时前
简单了解多路转接select
运维·服务器