首先,我们先要明白为什么需要设计这样一个定时器类?
在开发Linux网络程序时,通常需要维护多个定时器,如维护客户端心跳时间、检查多个数据包的超时重传等。如果采用Linux的SIGALARM信号实现,则会带来较大的系统开销,且不便于管理。
Muduo 的 TimerQueue 采用了最简单的实现(链表)来管理定时器,它的效率比不上常见的 binary heap 的做法,如果程序中大量(10 个以上)使用重复触发的定时器,或许值得考虑改用更高级的实现。由于目前还没有在一个程序里用过这么多定时器,暂时也不需要优化 TimerQueue。
(一)定时函数的选取
- 定时函数,用于让程序等待一段时间或安排计划任务:
- sleep
- alarm
- usleep
- nanosleep
- clock_nanosleep
- getitimer / setitimer
- timer_create / timer_settime / timer_gettime / timer_delete
- timerfd_create / timerfd_gettime / timerfd_settime
最终我们选择了下面的函数:
- #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)
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:
- #ifndef MUDUO_NET_TIMERID_H
- #define MUDUO_NET_TIMERID_H
- #include <muduo/base/copyable.h>
- namespace muduo
- {
- namespace net
- {
-
- class Timer;
-
-
- class TimerId : public muduo::copyable
- {
- public:
- TimerId()
- : timer_(NULL),
- sequence_(0)
- {
- }
-
- TimerId(Timer* timer, int64_t seq)
- : timer_(timer),
- sequence_(seq)
- {
- }
-
-
-
- friend class TimerQueue;
-
- private:
- Timer* timer_;
- int64_t sequence_;
- };
-
- }
- }
-
- #endif // MUDUO_NET_TIMERID_H
(2)Timer 是对定时操作的高度抽象,有多个数据成员,可以根据不同情况设置每个Timer超时的回调函数。
-
-
- class Timer : boost::noncopyable
- {
- public:
- Timer(const TimerCallback& cb, Timestamp when, double interval)
- : callback_(cb),
- expiration_(when),
- interval_(interval),
- repeat_(interval > 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_;
- const bool repeat_;
- const int64_t sequence_;
-
- static AtomicInt64 s_numCreated_;
- };
- }
- }
(3)TimerQueue的公有接口很简单,只有两个函数addTimer和cancel, TimerQueue 数据结构的选择,能快速根据当前时间找到已到期的定时器,也要高效的添加和删除Timer,因而可以用二叉搜索树,用map或者set。Timequeue
关注最早的定时器,
getExpired
返回所有的超时定时器列表,使用
low_bound
返回第一个值>=超时定时器的迭代器。
- class TimerQueue : boost::noncopyable
- {
- public:
- TimerQueue(EventLoop* loop);
- ~TimerQueue();
-
-
-
-
-
-
-
- TimerId addTimer(const TimerCallback& cb,
- Timestamp when,
- double interval);
-
- void cancel(TimerId timerId);
-
- private:
-
-
-
-
-
- typedef std::pair<Timestamp, Timer*> Entry;
- typedef std::set<Entry> TimerList;
- typedef std::pair<Timer*, int64_t> ActiveTimer;
- typedef std::set<ActiveTimer> ActiveTimerSet;
-
-
-
- void addTimerInLoop(Timer* timer);
- void cancelInLoop(TimerId timerId);
-
- void handleRead();
-
-
- std::vector<Entry> getExpired(Timestamp now);
- void reset(const std::vector<Entry>& expired, Timestamp now);
-
- bool insert(Timer* timer);
-
- EventLoop* loop_;
- const int timerfd_;
- Channel timerfdChannel_;
-
- TimerList timers_;
-
-
-
-
- ActiveTimerSet activeTimers_;
- bool callingExpiredTimers_;
- ActiveTimerSet cancelingTimers_;
- };
解释一个关键点:
rvo优化:
- struct Foo
- {
- Foo() { cout << "Foo ctor" << endl; }
- Foo(const Foo&) { cout << "Foo copy ctor" << endl; }
- void operator=(const Foo&) { cout << "Foo operator=" << endl; }
- ~Foo() { cout << "Foo dtor" << endl; }
- };
- Foo make_foo()
- {
- Foo f;
- return f;
- }
make_foo函数的返回对象,在linux下g++和vc++Release版本下,不会调用拷贝构造函数,而是直接返回对象,这就叫做rvo优化。但是vc下的debug版本没有做这个优化,读者可以自己测试一下。
如果对于上面的定时器类还是不理解,可以参考下这篇博客: http://blog.youkuaiyun.com/w616589292/article/details/45694987
没有那么多优化的技巧可能更容易理解!不过是用最小堆实现的哈。