muduo网络库源码分析-定时器


首先,我们先要明白为什么需要设计这样一个定时器类?

在开发Linux网络程序时,通常需要维护多个定时器,如维护客户端心跳时间、检查多个数据包的超时重传等。如果采用Linux的SIGALARM信号实现,则会带来较大的系统开销,且不便于管理

Muduo 的 TimerQueue 采用了最简单的实现(链表)来管理定时器,它的效率比不上常见的 binary heap 的做法,如果程序中大量(10 个以上)使用重复触发的定时器,或许值得考虑改用更高级的实现。由于目前还没有在一个程序里用过这么多定时器,暂时也不需要优化 TimerQueue。


(一)定时函数的选取

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. 定时函数,用于让程序等待一段时间或安排计划任务:  
  2. sleep   
  3. alarm   
  4. usleep   
  5. nanosleep   
  6. clock_nanosleep   
  7. getitimer / setitimer   
  8. timer_create / timer_settime / timer_gettime / timer_delete   
  9. timerfd_create / timerfd_gettime / timerfd_settime  


最终我们选择了下面的函数:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include <sys/timerfd.h>  
  2. int timerfd_create(int clockid, int flags);  
  3. // timerfd_create() creates a new timer object, and returns a file descriptor that refers to that timer.  
  4. int timerfd_settime(int fd, int flags, const struct itimerspec *new_value, struct itimerspec *old_value);  
  5. int timerfd_gettime(int fd, struct itimerspec *curr_value)  


timerfd_* 入选的原因:

(1)sleep / alarm / usleep 在实现时有可能用了信号 SIGALRM,在多线程程序中处理信号是个相当麻烦的事情,应当尽量避免。
(2)nanosleep 和 clock_nanosleep 是线程安全的,但是在非阻塞网络编程中,绝对不能用让线程挂起的方式来等待一段时间,程序会失去响应。正确的做法是注册一个时间回调函数。 
(3)getitimer 和 timer_create 也是用信号来 deliver 超时,在多线程程序中也会有麻烦。
(4)timer_create 可以指定信号的接收方是进程还是线程,算是一个进步,不过在信号处理函数(signal handler)能做的事情实在很受限。 

(5)timerfd_create 把时间变成了一个文件描述符,该“文件”在定时器超时的那一刻变得可读,这样就能很方便地融入到 select/poll 框架中,用统一的方式来处理 IO 事件和超时事件,这也正是 Reactor 模式的长处。


传统的Reactor 利用select/poll/epoll 的timeout 来实现定时功能,但poll 和epoll 的定时精度只有毫秒,远低于timerfd_settime 的定时精度。

(二)TimerId、Timer、TimerQueue分析

muduo的定时器由三个类实现,TimerId、Timer、TimerQueue,用户只能看到第一个类,其它两个都是内部实现细节
TimerQueue的接口很简单,只有两个函数addTimer和cancel。

EventLoop
runAt 在某个时刻运行定时器
runAfter 过一段时间运行定时器
runEvery 每隔一段时间运行定时器
cancel 取消定时器

TimerQueue数据结构的选择,能快速根据当前时间找到已到期的定时器,也要高效的添加和删除Timer,因而可以用二叉搜索树,用map或者set
typedef std::pair<Timestamp, Timer*> Entry;
typedef std::set<Entry> TimerList;

时序图:

分析:TimerQueue 中有多个定时器,一次性的和重复的,事件循环开始EventLoop::loop(),当最早到期定时器超时时,poll() 返回timerfd_ 的可读事件(timerfdChannel_),调用Channel::handleEvent(),调用readCallback_(receiveTime); 进而调用Channel::setReadCallback 注册的TimerQueue::handleRead(), 在函数内先read  掉timerfd_数据,避免一直触发可读事件,接着遍历TimerQueue中此时所有超时的定时器,调用每个定时器构造时传递的回调函数。

(1)TimerId 只有两个成员,TimerId主要用于取消Timer:
[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #ifndef MUDUO_NET_TIMERID_H  
  2. #define MUDUO_NET_TIMERID_H  
  3. #include <muduo/base/copyable.h>  
  4. namespace muduo  
  5. {  
  6. namespace net  
  7. {  
  8.   
  9. class Timer;  
  10.   
  11. /// An opaque identifier, for canceling Timer.  
  12. class TimerId : public muduo::copyable  
  13. {  
  14.  public:  
  15.   TimerId()  
  16.     : timer_(NULL),  
  17.       sequence_(0)  
  18.   {  
  19.   }  
  20.   
  21.   TimerId(Timer* timer, int64_t seq)  
  22.     : timer_(timer),  
  23.       sequence_(seq)  
  24.   {  
  25.   }  
  26.   
  27.   // default copy-ctor, dtor and assignment are okay  
  28.   
  29.   friend class TimerQueue;  
  30.   
  31.  private:  
  32.   Timer* timer_;  
  33.   int64_t sequence_;  
  34. };  
  35.   
  36. }  
  37. }  
  38.   
  39. #endif  // MUDUO_NET_TIMERID_H  
(2)Timer 是对定时操作的高度抽象,有多个数据成员,可以根据不同情况设置每个Timer超时的回调函数。
[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. /// Internal class for timer event.  
  2. ///  
  3. class Timer : boost::noncopyable  
  4. {  
  5.  public:  
  6.   Timer(const TimerCallback& cb, Timestamp when, double interval)  
  7.     : callback_(cb),  
  8.       expiration_(when),  
  9.       interval_(interval),  
  10.       repeat_(interval > 0.0),  
  11.       sequence_(s_numCreated_.incrementAndGet())  
  12.   { }  
  13.   
  14.   void run() const  
  15.   {  
  16.     callback_();  
  17.   }  
  18.   
  19.   Timestamp expiration() const  { return expiration_; }  
  20.   bool repeat() const { return repeat_; }  
  21.   int64_t sequence() const { return sequence_; }  
  22.   
  23.   void restart(Timestamp now);  
  24.   
  25.   static int64_t numCreated() { return s_numCreated_.get(); }  
  26.   
  27.  private:  
  28.   const TimerCallback callback_;        // 定时器回调函数  
  29.   Timestamp expiration_;                // 下一次的超时时刻  
  30.   const double interval_;               // 超时时间间隔,如果是一次性定时器,该值为0  
  31.   const bool repeat_;                   // 是否重复  
  32.   const int64_t sequence_;              // 定时器序号  
  33.   
  34.   static AtomicInt64 s_numCreated_;     // 定时器计数,当前已经创建的定时器数量  
  35. };  
  36. }  
  37. }  
(3)TimerQueue的公有接口很简单,只有两个函数addTimer和cancel, TimerQueue 数据结构的选择,能快速根据当前时间找到已到期的定时器,也要高效的添加和删除Timer,因而可以用二叉搜索树,用map或者set。Timequeue 关注最早的定时器, getExpired 返回所有的超时定时器列表,使用 low_bound 返回第一个值>=超时定时器的迭代器。
[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. class TimerQueue : boost::noncopyable  
  2. {  
  3.  public:  
  4.   TimerQueue(EventLoop* loop);  
  5.   ~TimerQueue();  
  6.   
  7.   ///  
  8.   /// Schedules the callback to be run at given time,  
  9.   /// repeats if @c interval > 0.0.  
  10.   ///  
  11.   /// Must be thread safe. Usually be called from other threads.  
  12.   // 一定是线程安全的,可以跨线程调用。通常情况下被其它线程调用。  
  13.   TimerId addTimer(const TimerCallback& cb,  
  14.                    Timestamp when,  
  15.                    double interval);  
  16.   
  17.   void cancel(TimerId timerId);  
  18.   
  19.  private:  
  20.   
  21.   // FIXME: use unique_ptr<Timer> instead of raw pointers.  
  22.   // unique_ptr是C++ 11标准的一个独享所有权的智能指针  
  23.   // 无法得到指向同一对象的两个unique_ptr指针  
  24.   // 但可以进行移动构造与移动赋值操作,即所有权可以移动到另一个对象(而非拷贝构造)  
  25.   typedef std::pair<Timestamp, Timer*> Entry;  
  26.   typedef std::set<Entry> TimerList;  
  27.   typedef std::pair<Timer*, int64_t> ActiveTimer;  
  28.   typedef std::set<ActiveTimer> ActiveTimerSet;  
  29.   
  30.   // 以下成员函数只可能在其所属的I/O线程中调用,因而不必加锁。  
  31.   // 服务器性能杀手之一是锁竞争,所以要尽可能少用锁  
  32.   void addTimerInLoop(Timer* timer);  
  33.   void cancelInLoop(TimerId timerId);  
  34.   // called when timerfd alarms  
  35.   void handleRead();  
  36.   // move out all expired timers  
  37.   // 返回超时的定时器列表  
  38.   std::vector<Entry> getExpired(Timestamp now);  
  39.   void reset(const std::vector<Entry>& expired, Timestamp now);  
  40.   
  41.   bool insert(Timer* timer);  
  42.   
  43.   EventLoop* loop_;     // 所属EventLoop  
  44.   const int timerfd_;  
  45.   Channel timerfdChannel_;  
  46.   // Timer list sorted by expiration  
  47.   TimerList timers_;    // timers_是按到期时间排序  
  48.   
  49.   // for cancel()  
  50.   // timers_与activeTimers_保存的是相同的数据  
  51.   // timers_是按到期时间排序,activeTimers_是按对象地址排序  
  52.   ActiveTimerSet activeTimers_;  
  53.   bool callingExpiredTimers_; /* atomic */  
  54.   ActiveTimerSet cancelingTimers_;  // 保存的是被取消的定时器  
  55. };  
解释一个关键点:
rvo优化:
[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. struct Foo     
  2. {     
  3.     Foo() { cout << "Foo ctor" << endl; }  
  4.     Foo(const Foo&) { cout << "Foo copy ctor" << endl; }  
  5.     void operator=(const Foo&) { cout << "Foo operator=" << endl; }   
  6.     ~Foo() { cout << "Foo dtor" << endl; }  
  7. };    
  8. Foo make_foo()     
  9. {  
  10.     Foo f;  
  11.     return f;  
  12. }    
make_foo函数的返回对象,在linux下g++和vc++Release版本下,不会调用拷贝构造函数,而是直接返回对象,这就叫做rvo优化。但是vc下的debug版本没有做这个优化,读者可以自己测试一下。

如果对于上面的定时器类还是不理解,可以参考下这篇博客:    http://blog.youkuaiyun.com/w616589292/article/details/45694987
没有那么多优化的技巧可能更容易理解!不过是用最小堆实现的哈。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值