[源码阅读]——Sylar服务器框架:定时器模块

定时器模块

    • 定时器模块概述
    • sylar定时器设计
      • Timer类
      • 定时器管理类
    • 总结

定时器模块概述

  sylar是基于epoll实现了定时器的功能,由于epoll的精度是毫秒级,所以其定时器的精度也是毫秒级。关于定时器有基于时间轮的设计、时间堆的设计等,在sylar中,其采用了最小堆的设计。具体关于定时器的讲解大家可以参考本人之前做的阅读笔记:
【阅读】《Linux高性能服务器编程》——第十一章。
  定时器通常至少包含两个成员:超时时间和任务回调函数。如果用链表作为容器来串联所有的定时器,则每个定时器还要包含指向下一个定时器的指针成员(单向链表)。基于时间堆的定时器设计则是将所有定时器中超时时间最小的一个定时器的超时值作为心搏间隔。这样一旦心搏函数tick被调用,超时时间最小的定时器必然到期,从而处理该定时器。依次反复,实现精确定时。
  在sylar中,其所有的定时器是根据超时时间的绝对时间点(系统时间+超时时间)进行排序,每次取出距离当前时间最近的一个时间点,计算超时需要等待的时间然后进行等待。当超时时间到的时候,啧获取当前的绝对是件,将定时器的最小堆中所有小于这个时间点的定时器都执行回调。

sylar定时器设计

  虽然定时器在整个系统中也是比较重要的一块,比如IO调度、Hook、判断超时等都用到了定时器模块,但是sylar对定时器的整体实现是较为简单的。
  sylar在定时器模块定义了两个类,分别是Timer定时器类和TimerManager定时器管理类。其中Timer定时器类包括定时器的绝对超时时间、回调函数、对应的管理类指针和一些重置、刷新、取消方法。而TimerManager定时器管理类则是对定时器进行管理,通过std::set来实现最小堆结构(set中的元素都是排序过的)。

Timer类

// 定时器
class Timer : public std::enable_shared_from_this {
friend class TimerManager;

public:
    typedef std::shared_ptr ptr;

    // 取消定时器
    bool cancel();
    // 刷新定时器的执行时间
    bool refesh();
    // 重置定时器时间
    bool reset(uint64_t ms, bool from_now);

private:
    // 构造函数
    Timer(uint64_t ms, std::function cb, bool recurring, TimerManager* manager);
    // next:执行的时间戳
    Timer(uint64_t next);

private:
    // 是否循环定时器
    bool m_recurring = false;
    // 执行周期
    uint64_t m_ms = 0;
    // 精确的执行时间
    uint64_t m_next = 0;
    // 回调函数
    std::function m_cb;
    // 定时器管理器
    TimerManager* m_manager = nullptr;

private:
    // 定时器比较仿函数
    struct Comparator {
        // 比较定时器的智能指针的大小(按执行时间排序)
        bool operator()(const Timer::ptr& lhs, const Timer::ptr& rhs) const;
    };
};

  整个定时器类的详细时间是比较简单的,其中主要是仿函数方面,因为对C++还有一点不熟悉,所以这里需要单独学习一下,其功能就是根据绝对超时时间比较两个Timer类对象。
  仿函数(Functor)又称为函数对象(Function Object)是一个能行使函数功能的类。仿函数的语法几乎和我们普通的函数调用一样,不过作为仿函数的类,都必须重载 operator() 运算符。因为调用仿函数,实际上就是通过类对象调用重载后的 operator() 运算符。
  接下来可以看一下仿函数的具体实现:

bool Timer::Comparator::operator()(const Timer::ptr& lhs, const Timer::ptr& rhs)const{
    if(!lhs && !rhs){
        return false;
    }
    if(!lhs){
        return true;
    }
    if(!rhs){
        return false;
    }
    if(lhs->m_next < rhs->m_next){
        return true;
    }
    if(rhs->m_next < lhs->m_next){
        return false;
    }
    return lhs.get() < rhs.get();
}

定时器管理类

  关于定时器管理类,其实主要便是维护一个定时器的集合std::set m_timers;当然也提供了一些相关的方法,比如通知IOManager更新epoll_wait超时、检测是否发生了超时、创建定时器等。

// 定时器管理器
class TimerManager{
friend class Timer;

public:
    typedef RWMutex RWMutexType;

    // 构造函数
    TimerManager();
    // 析构函数
    virtual ~TimerManager();

    // 添加定时器
    Timer::ptr addTimer(uint64_t ms, std::function cb, bool recurring = false);

    // 添加条件定时器
    Timer::ptr addConditionTimer(uint64_t ms, std::function cb,
                                std::weak_ptr weak_cond, bool recurring = false);
    
    // 到最近一个定时器执行的时间间隔
    uint64_t getNextTimer();

    // 获取需要执行的定时器的回调函数列表
    void listExpiredCb(std::vector >& cbs);

    // 是否有定时器
    bool hasTimer();

protected:
    // 有定时器插入到定时器首部时,执行该函数
    virtual void onTimerInsertedAtFront() = 0;

    // 添加定时器到管理器
    void addTimer(Timer::ptr val, RWMutexType::WriteLock& lock);

private:
    // 检测服务器时间是否被调后了
    bool detectClockRollover(uint64_t now_ms);

private:
    // mutex
    RWMutexType m_mutex;
    // 定时器集合
    std::set m_timers;
    // 是否触发onITimerInsertedAtFront
    bool m_tickled = false;
    // 上次执行时间
    uint64_t m_previouseTime = 0;
};

  定时器管理类的相关方法实现也是比较好阅读的,但是这里建议结合之前的idle再做阅读,因为开始的IOManager是没有结合Timer模块的,但是后续sylar将Timer模块和IOManager进行了一个整合,通过epoll_wait一方面检测是否有事件触发,一方面判断是否是超时。同时sylar是支持条件定时器的,及创建定时器时绑定变量,当定时器触发时,判断该变量是否有效,若无效啧定时器取消触发。

总结

  Timer模块本人觉得相对于前面的协程调度方面还是比较好理解的,主要是知道其定时器的设计即可,后面也会尝试改成时间轮的形式试一下。

你可能感兴趣的:(Sylar学习,服务器,linux,c++)