zoukankan      html  css  js  c++  java
  • muduo源码分析之定时器TimerQueue

     

      (muduo源码系列大多是我在看muduo源码的时候结合网上博客总结的,我尽可能多的是对源码注释)

    简介

             Muduo的定时器功能主要由三个class实现,TimerIdTimerTimerQueueTimerQueue的接口只有两个addTimer()cancel()addTimer()是提供给EventLoop使用的, EventLoop会把它封装成更好用的三个函数:runAt()runAfter()runEvery()

       大体实现

            muduo 定时器封装了 Timer.h 里面保存的是超时时间和回调函数, TimerQueue.h 使用set容器保存多个定时器, 然后在TimerQueue中使用timerfd_create创建一个timerfd句柄, 插入定时器A后先比较A的触发时间和TimerQueue的触发时间, 如果A的触发时间比其小就使用timerfd_settime重置TimerQueue的timerfd的触发时间, TimerQueue中的timerfd的触发时间永远与保存的定时器中触发时间最小的那个相同, 然后timerfd触发可读后, 遍历保存的多个定时器, 看看有没有同时到期的, 有执行回调函数

        TimerQueue的封装是为了让未到期的时间Timer有序的排列起来,这样,能够更具当前时间找到已经到期的Timer也能高效的添加和删除Timer

    所谓的到期与未到期,与当前在当前时间之前表示已经到期,之后则是未到期。为了方便计算,muduo重载了operator<主要是用来比较微秒大小。

    到期的时间应该被清除去执行相应的回调,未到期的时间则应该有序的排列起来。

     

    对于TimerQueue的数据结构,作者提出了几个方案。

    1.传统线性表,查找复杂度为O(n)

    2.二叉堆实现优先级队列,不过C++标准的make_heap()不能高效地完成删除操作。

     

    最终,为了防止时间相同所导致的Key相同的情况,使用setpair

    typedef std::pair<Timestamp, Timer*>Entry;
    
    typedef std::set<Entry>TimerList;
    
    TimerList timers_;

    Linux时间函数介绍

    linux中用以获取当前时间的的函数有:

    time(2) / time_t(秒)

    ftime(3) / struct timeb(毫秒)

    gettimeofday(2) / struct timeval(微秒)

    clock_gettime(2) / struct timespec(微秒)

     

    还有gmtime / localtime / timegm / mktime / strftime / struct tm等与当前时间无关的时间格式转换函数。

     

    定时函数

    sleep(3)

    alarm(3)

    usleep(3)

    nanosleep(2)

    clock_nanosleep(2)

    gettimer(2) / settitimer(2)

    timer_create(2) / timer_settime(2) / tiemr_gettime(2) / timer_delete(2)

    timerfd_create(2) / timerfd_gettime(2) / timerfd_settime(2)

     

    取舍如下

    1、计时只使用gettimeofday(2)来获取当前时间。

    2、定时只使用timerfd_*系列函数来处理定时任务。

    timerfd介绍

    这节介绍muduo中定时器的实现。先看一个2.6内核新增的有关定时的系统调用,基于这几个系统调用可以实现基于文件描述符的定时器。即可是定时,使文件描述符在某一特定时间可读。

     

    #include <sys/timerfd.h>
    
        int timerfd_create(int clockid, int flags);
    
        int timerfd_settime(int fd, int flags,
               const struct itimerspec * new_value,
               struct itimerspec * old_value);
    
        int timerfd_gettime(int fd, struct itimerspec *curr_value);

     

    1timerfd_create用于创建一个定时器文件,函数返回值是一个文件句柄fd

    2timerfd_settime用于设置新的超时时间,并开始计时。flag0表示相对时间,为1表示绝对时间。new_value为这次设置的新时间,old_value为上次设置的时间。返回0表示设置成功。

    3timerfd_gettime用于获得定时器距离下次超时还剩下的时间。如果调用时定时器已经到期,并且该定时器处于循环模式(设置超时时间时struct itimerspec::it_interval不为0),那么调用此函数之后定时器重新开始计时。

    TimerId介绍

    TimerId非常简单,它被设计用来取消Timer的,它的结构很简单,只有一个Timer指针和其序列号。

    // Copyright 2010, Shuo Chen.  All rights reserved.
    // http://code.google.com/p/muduo/
    //
    // Use of this source code is governed by a BSD-style license
    // that can be found in the License file.
    
    // Author: Shuo Chen (chenshuo at chenshuo dot com)
    //
    // This is a public header file, it must only include public header files.
    /*封装了timer类到构造和析构函数中,我的理解就是RAII的思想
     * TimerId非常简单,它被设计用来取消Timer的,它的结构很简单,只有一个Timer指针和其序列号。*/
    #ifndef MUDUO_NET_TIMERID_H
    #define MUDUO_NET_TIMERID_H
    
    #include <muduo/base/copyable.h>
    
    namespace muduo {
        namespace net {
    
            class Timer;
    
    ///
    /// An opaque identifier, for canceling Timer.
    ///
            class TimerId : public muduo::copyable {
            public:
                TimerId()
                        : timer_(NULL),
                          sequence_(0) {
                }
    
                TimerId(Timer *timer, int64_t seq)
                        : timer_(timer),
                          sequence_(seq) {
                }
    
                // default copy-ctor, dtor and assignment are okay
    
                friend class TimerQueue;//友元,就是可以访问类的私有成员变量,但不是类中的成员
    
            private:
                Timer *timer_;
                int64_t sequence_;
            };
    
        }
    }
    
    #endif  // MUDUO_NET_TIMERID_H

    Timer

    Timer封装了定时器的一些参数,例如超时回调函数、超时时间、定时器是否重复、重复间隔时间、定时器的序列号。其函数大都是设置这些参数,run()用来调用回调函数,restart()用来重启定时器(如果设置为重复)。其源码相对简单

     

    // Copyright 2010, Shuo Chen.  All rights reserved.
    // http://code.google.com/p/muduo/
    //
    // Use of this source code is governed by a BSD-style license
    // that can be found in the License file.
    
    // Author: Shuo Chen (chenshuo at chenshuo dot com)
    //
    // This is an internal header file, you should not include this.
    /*计时器类*/
    #ifndef MUDUO_NET_TIMER_H
    #define MUDUO_NET_TIMER_H
    
    #include <boost/noncopyable.hpp>
    
    #include <muduo/base/Atomic.h>
    #include <muduo/base/Timestamp.h>
    #include <muduo/net/Callbacks.h>
    
    namespace muduo {
        namespace net {
    ///
    /// Internal class for timer event.
    ///
            class Timer : boost::noncopyable {
            public:
                Timer(const TimerCallback &cb, Timestamp when, double interval)
                        : callback_(cb),//回调函数
                          expiration_(when),//超时时间
                          interval_(interval),//如果重复,间隔时间
                          repeat_(interval > 0.0),//如果间隔大于0,就重复
                          sequence_(s_numCreated_.incrementAndGet()) {}//当前定时器的序列号
                //调用回调函数.
                void run() const {
                    callback_();
                }
    
                Timestamp expiration() const { return expiration_; }//返回超时时刻
                bool repeat() const { return repeat_; }//返回是否重复
                int64_t sequence() const { return sequence_; }//返回定时器序号
    
                void restart(Timestamp now);//重新开始
    
                static int64_t numCreated() { return s_numCreated_.get(); }//返回最新的序号值
    
            private:
                const TimerCallback callback_;        // 定时器回调函数
                Timestamp expiration_;                // 下一次的超时时间戳类
                const double interval_;                // 超时时间间隔,如果是一次性定时器,该值为0
                const bool repeat_;                    // 是否重复
                const int64_t sequence_;                // 定时器序号,不会重复
    
                static AtomicInt64 s_numCreated_;        // 定时器计数,当前已经创建的定时器数量
            };
        }
    }
    #endif  // MUDUO_NET_TIMER_H

     

     

    Timestamp

    时间戳一般用来唯一地标识某一刻的时间,通常是指格林威治时间19700101000000(北京时间19700101080000)起至现在的总毫秒数。

     

    Timestamp类的参数有一个常量kMicroSecondsPerSecond表示每秒所对应的微秒数,成员变量microSecondsSinceEpoch_表示到1970-01-01 00:00:00 UTC的微秒数。成员函数包括swap()交换操作,toString()toFormattedString()将时间转换为string类型或指定格式,valid()判断Timestamp是否有效,invalid()返回一个无效的Timestampnow()返回当前时间的TimestampsecondsSinceEpoch()/microSecondsSinceEpoch()返回到1970-01-01 00:00:00 UTC的秒数/微秒数。

    Timestamp.h源码(带注释)

    #ifndef MUDUO_BASE_TIMESTAMP_H
    #define MUDUO_BASE_TIMESTAMP_H
    
    /*时间戳函数,我的理解是创建一个microSecondsSinceEpoch_为0的结构体,
     *然后每次通过这个结构体创建子的microSecondsSinceEpoch_为当前时间的结构体,并且子结构体都在microSecondsSinceEpoch_为0的结构体
     *中进行运算操作*/
    #include <muduo/base/copyable.h>
    #include <muduo/base/Types.h>
    
    #include <boost/operators.hpp>
    
    namespace muduo {
    
    ///Timestamp
    /// Time stamp in UTC, in microseconds resolution.
    ///
    /// This class is immutable.
    /// It's recommended to pass it by value, since it's passed in register on x64.
    ///
        class Timestamp : public muduo::copyable,
                          public boost::less_than_comparable<Timestamp>//继承这个类,对于<,>,<=,>=这些运算符号,只需要定义
            //<号,其他的都可以自动帮你定义了
        {
        public:
            ///
            /// Constucts an invalid Timestamp.
            ///
            Timestamp()
                    : microSecondsSinceEpoch_(0) {
            }
    
            ///
            /// Constucts a Timestamp at specific time
            ///
            /// @param microSecondsSinceEpoch
            explicit Timestamp(int64_t microSecondsSinceEpoch);
    
            void swap(Timestamp &that)//将两个Timestamp类中的microSecondsSinceEpoch_变量交换
            {
                std::swap(microSecondsSinceEpoch_, that.microSecondsSinceEpoch_);
            }
    
            // default copy/assignment/dtor are Okay
    
            string toString() const;
    
            string toFormattedString() const;
    
            bool valid() const { return microSecondsSinceEpoch_ > 0; }//判断microSecondsSinceEpoch_是否有效,大于0就有效
    
            // for internal usage.
            int64_t microSecondsSinceEpoch() const { return microSecondsSinceEpoch_; }//返回microSecondsSinceEpoch_
            time_t secondsSinceEpoch() const//返回以秒为单位的microSecondsSinceEpoch_
            { return static_cast<time_t>(microSecondsSinceEpoch_ / kMicroSecondsPerSecond); }
    
            ///
            /// Get time of now.
            ///
            static Timestamp now();//创建一个当前的时间的Timestamp结构体
            static Timestamp invalid();//创建一个microSecondsSinceEpoch_=0的Timestamp结构体,都是静态函数,可以直接调用
    
            static const int kMicroSecondsPerSecond = 1000 * 1000;
    
        private:
            int64_t microSecondsSinceEpoch_;//表示到1970-01-01 00:00:00 UTC的微秒数。
        };
    
    /*放在类外重载,但是普通数据类型可以使用原来的,遇到特定类型才会使用这个*/
        inline bool operator<(Timestamp lhs, Timestamp rhs)//只需要定义<号,其他都自动定义,less_than_comparable模板作用
        {
            return lhs.microSecondsSinceEpoch() < rhs.microSecondsSinceEpoch();
        }
    
        inline bool operator==(Timestamp lhs, Timestamp rhs) {
            return lhs.microSecondsSinceEpoch() == rhs.microSecondsSinceEpoch();
        }
    
    ///
    /// Gets time difference of two timestamps, result in seconds.
    ///
    /// @param high, low
    /// @return (high-low) in seconds
    /// @c double has 52-bit precision, enough for one-microseciond
    /// resolution for next 100 years.
    
    //计算两个Timestamp之间的差,以秒为单位
        inline double timeDifference(Timestamp high, Timestamp low) {
            int64_t diff = high.microSecondsSinceEpoch() - low.microSecondsSinceEpoch();
            return static_cast<double>(diff) / Timestamp::kMicroSecondsPerSecond;
        }
    
    ///
    /// Add @c seconds to given timestamp.
    ///
    /// @return timestamp+seconds as Timestamp
    ///
    //加时间
        inline Timestamp addTime(Timestamp timestamp, double seconds) {
            int64_t delta = static_cast<int64_t>(seconds * Timestamp::kMicroSecondsPerSecond);
            return Timestamp(timestamp.microSecondsSinceEpoch() + delta);
        }
    
    }
    #endif  // MUDUO_BASE_TIMESTAMP_H

      Timestamp.cc源码(带注释)

      

    #include <muduo/base/Timestamp.h>
    
    #include <sys/time.h>
    #include <stdio.h>
    
    #define __STDC_FORMAT_MACROS
    
    #include <inttypes.h>
    
    #undef __STDC_FORMAT_MACROS
    
    #include <boost/static_assert.hpp>
    
    using namespace muduo;
    
    BOOST_STATIC_ASSERT(sizeof(Timestamp) == sizeof(int64_t));
    
    Timestamp::Timestamp(int64_t microseconds)
            : microSecondsSinceEpoch_(microseconds) {
    }
    
    string Timestamp::toString() const//转化成xxx.xxx秒的格式
    {
        char buf[32] = {0};
        int64_t seconds = microSecondsSinceEpoch_ / kMicroSecondsPerSecond;
        int64_t microseconds = microSecondsSinceEpoch_ % kMicroSecondsPerSecond;
        snprintf(buf, sizeof(buf) - 1, "%"
        PRId64
        ".%06"
        PRId64
        "", seconds, microseconds);
        return buf;
    }
    
    //将时间加上1900年1月1日之后转成"年月日 小时:分钟:秒"的格式
    string Timestamp::toFormattedString() const {
        char buf[32] = {0};
        time_t seconds = static_cast<time_t>(microSecondsSinceEpoch_ / kMicroSecondsPerSecond); // 秒数
        int microseconds = static_cast<int>(microSecondsSinceEpoch_ % kMicroSecondsPerSecond);
        struct tm tm_time;// 微秒数
        // 把time_t结构中的信息转换成真实世界所使用的时间日期,存储在tm_time结构中
        gmtime_r(&seconds, &tm_time);//将总秒数转换成————多少年多少月多少日多少小时多少分多少秒为单位
        // 格式化输出时间戳
        snprintf(buf, sizeof(buf), "%4d%02d%02d %02d:%02d:%02d.%06d",
                 tm_time.tm_year + 1900, tm_time.tm_mon + 1, tm_time.tm_mday,
                 tm_time.tm_hour, tm_time.tm_min, tm_time.tm_sec,
                 microseconds);//总秒数加上1900年1月1日然后转换成固定格式
        return buf;
    }
    
    Timestamp Timestamp::now() {
        struct timeval tv;
        gettimeofday(&tv, NULL);//得到的是系统从当前时间从1900年1月1日开始算起的总秒数
        int64_t seconds = tv.tv_sec;
        return Timestamp(seconds * kMicroSecondsPerSecond + tv.tv_usec);
        //返回一个Timestamp结构体,相当于创建一个当前时间的Timestamp结构体
    }
    
    Timestamp Timestamp::invalid() {
        return Timestamp();
    }

    TimerQueue

    虽然TimerQueue中有Queue,但是其实现时基于Set的,而不是Queue。这样可以高效地插入、删除定时器,且找到当前已经超时的定时器。TimerQueuepublic接口只有两个,添加和删除。

     

    void addTimerInLoop(Timer* timer);
    
    void cancelInLoop(TimerId timerId);

    定时器管理类,其中timer类就是TimerQueue需要管理的元素,而timerId就是一个简单的timer封装,避免销毁和创建操作

    但是要注意的是timer并没有自己计时的功能,所以需要依靠timerfd这个系统函数统一计时timerfd是一个系统计时函数,当所设置的时间到了,会通过timerfd这个文件描述符进行提示通信,而其他计时函数可能是通过信号量,或者其他方式,但是都没有文件描述符好,并且也可以用timerfd监听,具体原因可以查看一下博客的网络库定时器实现

    如何使用timerfd来为所有的计时器计时:timerfd每次都设置在计时器列表中到期时间最近的那个到期时间,这样timerfd到期以后,也就是最近的那个计时器到期,所以每次都是手动重置timerfd的计时时间,为最近的计时器到期时间

     

    内部有channel,和timerfd关联。添加新的Timer后,在超时后,timerfd可读,会处理channel事件,之后调用Timer的回调函数;在timerfd的事件处理后,还有检查一遍超时定时器,如果其属性为重复还有再次添加到定时器集合中。

    内部有两种类型的Set

    typedef std::pair<Timestamp, Timer*> Entry;
    
    typedef std::set<Entry> TimerList;
    
    typedef std::pair<Timer*, int64_t> ActiveTimer;
    
    typedef std::set<ActiveTimer> ActiveTimerSet;

     

    下面给出TimerQueue.hTimerQueue.cc源码分析有详细的注释

    TimerQueue.h

    // Copyright 2010, Shuo Chen.  All rights reserved.
    // http://code.google.com/p/muduo/
    //
    // Use of this source code is governed by a BSD-style license
    // that can be found in the License file.
    
    // Author: Shuo Chen (chenshuo at chenshuo dot com)
    //
    // This is an internal header file, you should not include this.
    /* 定时器管理类,其中timer类就是TimerQueue需要管理的元素,而timerId就是一个简单的timer封装,避免销毁和创建操作
     * 但是要注意的是timer并没有自己计时的功能,所以需要依靠timerfd这个系统函数统一计时
     * timerfd是一个系统计时函数,当所设置的时间到了,会通过timerfd这个文件描述符进行提示通信,而其他计时函数可能是通过信号量,或者
     * 其他方式,但是都没有文件描述符好,并且也可以用timerfd监听,具体原因可以查看一下博客的网络库定时器实现
     * 如何使用timerfd来为所有的计时器计时:timerfd每次都设置在计时器列表中到期时间最近的那个到期时间,这样timerfd到期以后,也就是最近的那个计时器到期
     * 所以每次都是手动重置timerfd的计时时间,为最近的计时器到期时间
     * ?timestamp::now获得的时间是从1960年1月1日开始的,但是timerfd据说是从系统开机的那一刻开始的,那么在设置timefd时时间不统一怎么办
     * 注意在timerfd设置延时的时候,使用的是相对时间,所以无所谓最终时间是多少,只要相对时间没问题就好了
     * ?重置timerfd导致延时怎么办
     * ?关于线程执行,究竟哪些函数靠IO线程来执行
     * 
     * */
    #ifndef MUDUO_NET_TIMERQUEUE_H
    #define MUDUO_NET_TIMERQUEUE_H
    
    #include <set>
    #include <vector>
    
    #include <boost/noncopyable.hpp>
    
    #include <muduo/base/Mutex.h>
    #include <muduo/base/Timestamp.h>
    #include <muduo/net/Callbacks.h>
    #include <muduo/net/Channel.h>
    
    namespace muduo {
        namespace net {
    
            class EventLoop;
    
            class Timer;
    
            class TimerId;
    
    ///
    /// A best efforts timer queue.
    /// No guarantee that the callback will be on time.
    ///
            class TimerQueue : boost::noncopyable {
            public:
                TimerQueue(EventLoop *loop);
    
                ~TimerQueue();
    
                ///
                /// Schedules the callback to be run at given time,
                /// repeats if @c interval > 0.0.
                ///
                /// Must be thread safe. Usually be called from other threads.
                // 一定是线程安全的,可以跨线程调用。通常情况下被其它线程调用。
                TimerId addTimer(const TimerCallback &cb,
                                 Timestamp when,
                                 double interval);
    
                void cancel(TimerId timerId);
    
            private:
    
                // FIXME: use unique_ptr<Timer> instead of raw pointers.
                // unique_ptr是C++ 11标准的一个独享所有权的智能指针
                // 无法得到指向同一对象的两个unique_ptr指针
                // 但可以进行移动构造与移动赋值操作,即所有权可以移动到另一个对象(而非拷贝构造)
                typedef std::pair<Timestamp, Timer *> Entry;
                typedef std::set <Entry> TimerList;
                typedef std::pair<Timer *, int64_t> ActiveTimer;
                typedef std::set <ActiveTimer> ActiveTimerSet;
                //set中存储的是pair类型,那么默认先按照pair的第一个元素排序,如果相同,再按照第二个元素排序。
                //所以这两种set都是存放定时器的列表,但是一个根据定时器的到时时间来存储,
                //一个根据定时器地址来存储,但是存储的定时器都是同一个,目的是为了区分同一到期时间的定时器???
    
                // 以下成员函数只可能在其所属的I/O线程中调用,因而不必加锁。
                // 服务器性能杀手之一是锁竞争,所以要尽可能少用锁
                void addTimerInLoop(Timer *timer);
    
                void cancelInLoop(TimerId timerId);
    
                // called when timerfd alarms
                void handleRead();//timerfdChannel_的读函数
                // move out all expired timers
                // 返回超时的定时器列表
                std::vector <Entry> getExpired(Timestamp now);
    
                void reset(const std::vector <Entry> &expired, Timestamp now);
    
                bool insert(Timer *timer);
    
                EventLoop *loop_;        // 所属EventLoop
                const int timerfd_;
                //过一段事件,就筛选一次,看看TimerList中有多少定时器到时间了,就处理一下,但是这样延迟很高,不太理解
                Channel timerfdChannel_;//与timefd绑定
                // Timer list sorted by expiration
                TimerList timers_;    // timers_是按到期时间排序,也是存放未到时间的定时器
    
                // for cancel()
                // timers_与activeTimers_保存的是相同的数据
                // timers_是按到期时间排序,activeTimers_是按对象地址排序
                ActiveTimerSet activeTimers_;//还未到时间的定时器,这里面存放的定时器是和timers_一样的,只是顺序不同
                bool callingExpiredTimers_; /* atomic *///是否在处理过期定时器的标志
                ActiveTimerSet cancelingTimers_;    // 保存的是被取消的定时器
                // 用这个列表的作用是,当出现一个循环的计时器被取消时,就要通过reset函数中对
                //ActiveTimerSet列表来暂停对这个计时器的重置
            };
    
        }
    }
    #endif  // MUDUO_NET_TIMERQUEUE_H

    TimerQueue.cc

     

    // Copyright 2010, Shuo Chen.  All rights reserved.
    // http://code.google.com/p/muduo/
    //
    // Use of this source code is governed by a BSD-style license
    // that can be found in the License file.
    
    // Author: Shuo Chen (chenshuo at chenshuo dot com)
    
    #define __STDC_LIMIT_MACROS
    
    #include <muduo/net/TimerQueue.h>
    
    #include <muduo/base/Logging.h>
    #include <muduo/net/EventLoop.h>
    #include <muduo/net/Timer.h>
    #include <muduo/net/TimerId.h>
    
    #include <boost/bind.hpp>
    
    #include <sys/timerfd.h>
    
    namespace muduo {
        namespace net {
            namespace detail {
    
                // 创建定时器
                int createTimerfd() {
                    int timerfd = ::timerfd_create(CLOCK_MONOTONIC,
                                                   TFD_NONBLOCK | TFD_CLOEXEC);//CLOCK_MONOTONIC参数表明计时器的时间是从系统打开开始计时的
                    //CLOCK_MONOTONIC表示的是时间类型
                    if (timerfd < 0) {
                        LOG_SYSFATAL << "Failed in timerfd_create";
                    }
                    return timerfd;
                }
    
                // 计算超时时刻与当前时间的时间差
                struct timespec howMuchTimeFromNow(Timestamp when) {
                    int64_t microseconds = when.microSecondsSinceEpoch()
                                           - Timestamp::now().microSecondsSinceEpoch();
                    if (microseconds < 100) {
                        microseconds = 100;
                    }
                    struct timespec ts;
                    ts.tv_sec = static_cast<time_t>(
                            microseconds / Timestamp::kMicroSecondsPerSecond);
                    ts.tv_nsec = static_cast<long>(
                            (microseconds % Timestamp::kMicroSecondsPerSecond) * 1000);
                    return ts;
                }
    
                // 清除定时器,避免一直触发//处理超时事件。超时后,timerfd变为可读
                void readTimerfd(int timerfd, Timestamp now) {
                    uint64_t howmany;
                    ssize_t n = ::read(timerfd, &howmany, sizeof howmany);
                    LOG_TRACE << "TimerQueue::handleRead() " << howmany << " at " << now.toString();
                    if (n != sizeof howmany) {
                        LOG_ERROR << "TimerQueue::handleRead() reads " << n << " bytes instead of 8";
                    }
                }
    
                // 重置定时器的超时时间(不是周期性的定时器,时间到expiration就结束了)
                // 在这里面itimerspec.it_interval都是设置的0,每次都是计时结束以后手动重新设置,然后再计时的。
                void resetTimerfd(int timerfd, Timestamp expiration) {
                    // wake up loop by timerfd_settime()
                    struct itimerspec newValue;
                    struct itimerspec oldValue;
                    bzero(&newValue, sizeof newValue);
                    bzero(&oldValue, sizeof oldValue);
                    newValue.it_value = howMuchTimeFromNow(expiration);
                    int ret = ::timerfd_settime(timerfd, 0, &newValue, &oldValue);
                    if (ret) {
                        LOG_SYSERR << "timerfd_settime()";
                    }
                }
    
            }
        }
    }
    
    using namespace muduo;
    using namespace muduo::net;
    using namespace muduo::net::detail;
    
    TimerQueue::TimerQueue(EventLoop *loop)
            : loop_(loop),
              timerfd_(createTimerfd()),
              timerfdChannel_(loop, timerfd_),//timerfd相关的channel
              timers_(),
              callingExpiredTimers_(false) {
        timerfdChannel_.setReadCallback(
                boost::bind(&TimerQueue::handleRead, this));
        // we are always reading the timerfd, we disarm it with timerfd_settime.
        timerfdChannel_.enableReading();//设置关注读事件,并且加入epoll队列
    }
    
    TimerQueue::~TimerQueue() {
        ::close(timerfd_);
        // do not remove channel, since we're in EventLoop::dtor();
        for (TimerList::iterator it = timers_.begin();
             it != timers_.end(); ++it) {
            delete it->second;
        }
    }
    
    TimerId TimerQueue::addTimer(const TimerCallback &cb,
                                 Timestamp when,
                                 double interval)//创建并增加Timer进队列中
    {
        Timer *timer = new Timer(cb, when, interval);
    
        loop_->runInLoop(
                boost::bind(&TimerQueue::addTimerInLoop, this, timer));
    
        //addTimerInLoop(timer);
        return TimerId(timer, timer->sequence());
    }
    
    void TimerQueue::cancel(TimerId timerId)//取消
    {
        loop_->runInLoop(
                boost::bind(&TimerQueue::cancelInLoop, this, timerId));
        //cancelInLoop(timerId);
    }
    
    void TimerQueue::addTimerInLoop(Timer *timer) {
        loop_->assertInLoopThread();
        // 插入一个定时器,有可能会使得最早到期的定时器发生改变
        bool earliestChanged = insert(timer);
    
        if (earliestChanged) {
            // 重置timefd定时器的超时时刻(timerfd_settime)
            resetTimerfd(timerfd_, timer->expiration());
        }
    }
    
    void TimerQueue::cancelInLoop(TimerId timerId)//取消的回调函数
    //取消计时器,就是把该计时器从两个队列中删除,
    //现在有一种特殊情况,就是如果刚好在处理定时器的过程中,并且这个要取消的定时器就是在被处理的,并且是循环定时器,那么如果不加入cancelingTimers_列表
    //就会出现,在重置时又把这个定时器重启了,但是这个定时器应该是要被取消的
    {
        loop_->assertInLoopThread();
        assert(timers_.size() == activeTimers_.size());
        ActiveTimer timer(timerId.timer_, timerId.sequence_);
        // 查找该定时器
        ActiveTimerSet::iterator it = activeTimers_.find(timer);
        if (it != activeTimers_.end()) {
            size_t n = timers_.erase(Entry(it->first->expiration(), it->first));
            assert(n == 1);
            (void) n;
            delete it->first; // FIXME: no delete please,如果用了unique_ptr,这里就不需要手动删除了
            activeTimers_.erase(it);
        }//用activeTimers_列表来搜索,然后找到先删除timers_,再删除activeTimers_
        else if (callingExpiredTimers_)
            //如果在未到时间的定时器中没有找到,并且线程正在处理过期的定时器,那么可能这个定时器正在被处理,就将这些定时器放到cancelingTimers_数组中
        {
            // 已经到期,并且正在调用回调函数的定时器,为了在重置时,避免被重置,而是被忽略
            cancelingTimers_.insert(timer);
        }
        assert(timers_.size() == activeTimers_.size());
    }
    
    void TimerQueue::handleRead()//TimerChannel的回调函数,也就是当timefd定时器到时的时候,就会调用这个函数
    {
        loop_->assertInLoopThread();
        Timestamp now(Timestamp::now());
        readTimerfd(timerfd_, now);        // 清除该事件,避免一直触发
    
        // 获取该时刻之前所有的定时器列表(即超时定时器列表)
        std::vector <Entry> expired = getExpired(now);
    
        callingExpiredTimers_ = true;//处理到期的定时器
        cancelingTimers_.clear();//每次处理前,把要取消的定时器列表清空
        // safe to callback outside critical section
        for (std::vector<Entry>::iterator it = expired.begin();
             it != expired.end(); ++it) {
            // 这里回调定时器timer处理函数
            it->second->run();
        }
        callingExpiredTimers_ = false;
    
        // 不是一次性定时器,需要重启
        reset(expired, now);//如果之前处理定时器回调函数时间较长,那么在这段时间中,已经有定时器到期了,轻则产生延迟,重则
    }
    
    // rvo
    std::vector <TimerQueue::Entry> TimerQueue::getExpired(Timestamp now)//得到已经过期的计时器
    {
        assert(timers_.size() == activeTimers_.size());
        std::vector <Entry> expired;//存放已经过期的定时器
        Entry sentry(now, reinterpret_cast<Timer *>(UINTPTR_MAX));//我理解是找了一个指针可以取到的最大数,为了避免和其他指针冲突,
        //因为这个指针没有什么意义,仅仅是为了构成一个Entry结构体,有意义的是第一个元素now
    
        // 返回第一个未到期的Timer的迭代器
        // lower_bound的含义是返回第一个值>=sentry的元素的iterator
        // 即*end >= sentry,从而end->first > now
        TimerList::iterator end = timers_.lower_bound(sentry);
        assert(end == timers_.end() || now < end->first);
        // 将到期的定时器插入到expired中
        std::copy(timers_.begin(), end, back_inserter(expired));//back_inserter是迭代器的一种操作,效果和expired.push_back()一样
        // 从timers_中移除到期的定时器
        timers_.erase(timers_.begin(), end);
    
        // 从activeTimers_中移除到期的定时器
        for (std::vector<Entry>::iterator it = expired.begin();
             it != expired.end(); ++it) {
            ActiveTimer timer(it->second, it->second->sequence());
            size_t n = activeTimers_.erase(timer);
            assert(n == 1);
            (void) n;//避免编译器出现变量n未使用的警告???
        }
    
        assert(timers_.size() == activeTimers_.size());
        return expired;
    }
    
    void TimerQueue::reset(const std::vector <Entry> &expired, Timestamp now)//重启两种定时器,一种是timefd,另外一种是定时器列表中需要重复的定时器
    {
        Timestamp nextExpire;
        //重启定时器列表中过期的定时器,如果需要重复的话
        for (std::vector<Entry>::const_iterator it = expired.begin();
             it != expired.end(); ++it) {
            ActiveTimer timer(it->second, it->second->sequence());
            // 如果是重复的定时器并且是未取消定时器,则重启该定时器
            if (it->second->repeat()
                && cancelingTimers_.find(timer) == cancelingTimers_.end()) {
                it->second->restart(now);
                insert(it->second);
            } else//不需要重复就删除这个定时器
            {
                // 一次性定时器或者已被取消的定时器是不能重置的,因此删除该定时器
                // FIXME move to a free list
                delete it->second; // FIXME: no delete please
            }
        }
        //重启timefd,设置的时间就是定时器列表中最快到期的时间
        if (!timers_.empty()) {
            // 获取最早到期的定时器超时时间
            nextExpire = timers_.begin()->second->expiration();
        }
    
        if (nextExpire.valid()) {
            // 重置定时器的超时时刻(timerfd_settime)
            resetTimerfd(timerfd_, nextExpire);
        }
    }
    
    bool TimerQueue::insert(Timer *timer)//把定时器插入到timers_和activeTimers_队列中去
    {
        loop_->assertInLoopThread();
        assert(timers_.size() == activeTimers_.size());
        // 最早到期时间是否改变
        bool earliestChanged = false;//这个变量的意义是显示最早到期时间是否改变,通俗点说就是这个插入的定时器的位置在timers_的
        //首位,也就是这个插入的定时器的到期时间是timers_中已经存储的定时器中最早的,那么这个标志位就会置true
        Timestamp when = timer->expiration();//超时时刻
        TimerList::iterator it = timers_.begin();
        // 如果timers_为空或者when小于timers_中的最早到期时间
        if (it == timers_.end() || when < it->first) {
            earliestChanged = true;//表示定时器最早,所以置true
        }
        //要分别插入到两个set中
        {
            // 插入到timers_中
            std::pair<TimerList::iterator, bool> result
                    = timers_.insert(Entry(when, timer));
            assert(result.second);
            (void) result;
        }
        {
            // 插入到activeTimers_中
            std::pair<ActiveTimerSet::iterator, bool> result
                    = activeTimers_.insert(ActiveTimer(timer, timer->sequence()));
            assert(result.second);
            (void) result;
        }
    
        assert(timers_.size() == activeTimers_.size());
        return earliestChanged;//返回最早到期的时间有没有改变
    }

     

  • 相关阅读:
    redis后台启动配置
    Go匿名函数
    Java中的ExceptionInInitializerError异常及解决方法
    数据库备份工具mysqldump重要参数详解
    Nginx初识
    找不到或无法加载主类
    类加载机制与反射(二)
    JavaScript 数据结构与算法之美
    JavaScript 数据结构与算法之美
    JavaScript 数据结构与算法之美
  • 原文地址:https://www.cnblogs.com/qldabiaoge/p/12697439.html
Copyright © 2011-2022 走看看