引言
本文章参考《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;
}
}
总结
本篇文章到这里就结束了!!!希望可以帮助大家理解~~~~