让EventLoop 能够处理定时器事件
用于让程序等待一段时间或安排计划任务:
sleep
alarm usleep
nanosleep
clock_nanosleep
getitimer / setitimer
timer_create / timer_settime / timer_gettime / timer_delete
timerfd_create / timerfd_gettime / timerfd_settime 选择这种方法。
sleep / alarm / usleep 在实现时有可能用了信号 SIGALRM,在多线程程序中处理信号是个相当麻烦的事情,应当尽量避免 。
nanosleep 和 clock_nanosleep 是线程安全的,但是在非阻塞网络编程中,绝对不能用让线程挂起的方式来等待一段时间,程序会失去响应。正确的做法是注册一个时间回调函数。
getitimer 和 timer_create 也是用信号来 deliver 超时,在多线程程序中也会有麻烦。
timer_create 可以指定信号的接收方是进程还是线程,算是一个进步,不过在信号处理函数(signal handler)能做的事情实在很受限。
timerfd_create 把时间变成了一个文件描述符,该“文件”在定时器超时的那一刻变得可读,这样就能很方便地融入到 select/poll 框架中,用统一的方式来处理 IO 事件和超时事件,这也正是 Reactor 模式的长处。
如果要处理信号的话,也可以让信号转换成文件描述符来处理,signalfd
#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)
示例:
#include <muduo/net/Channel.h> #include <muduo/net/EventLoop.h> #include <boost/bind.hpp> #include <stdio.h> #include <sys/timerfd.h> using namespace muduo; using namespace muduo::net; EventLoop* g_loop; int timerfd; void timeout(Timestamp receiveTime) { printf("Timeout!\n"); uint64_t howmany; ::read(timerfd, &howmany, sizeof howmany); // 这里一定要读走,不然处于高电平状态一直触发 g_loop->quit(); } int main(void) { EventLoop loop; g_loop = &loop; timerfd = ::timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC); Channel channel(&loop, timerfd); channel.setReadCallback(boost::bind(timeout, _1)); channel.enableReading(); struct itimerspec howlong; bzero(&howlong, sizeof howlong); howlong.it_value.tv_sec = 1; ::timerfd_settime(timerfd, 0, &howlong, NULL); loop.loop(); ::close(timerfd); }
muduo 定时器由三个类实现,TimerId、Timer、TimerQueue,用户只能看到第一个类,其它两个都是内部实现细节。
timer 定时器的高层次抽象,并没有实际的定时器函数
#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), 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
#include <muduo/net/Timer.h> using namespace muduo; using namespace muduo::net; AtomicInt64 Timer::s_numCreated_; void Timer::restart(Timestamp now) { if (repeat_) { // 重新计算下一个超时时刻 expiration_ = addTime(now, interval_); } else { expiration_ = Timestamp::invalid(); } }
TimerQueue的接口很简单,只有两个函数addTimer和cancel。调用addTimer 返回timerld。添加定时器, timerld 是外部类。调用cancel 传递timerld 取消定时器。实际使用也不用TimerQueue的方法,使用的时候用EventLoop提供的函数。TimerQueue 会调用实际的定时器函数
EventLoop
runAt 在某个时刻运行定时器
runAfter 过一段时间运行定时器
runEvery 每隔一段时间运行定时器
cancel 取消定时器
TimerQueue数据结构的选择,能快速根据当前时间找到已到期的定时器,也要高效的添加和删除Timer,因而可以用二叉搜索树,用map或者set
typedef std::pair<Timestamp, Timer*> Entry;
typedef std::set<Entry> TimerList;
#include <iostream> using namespace std; 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; // RVO 优化没有执行拷贝构造 //return Foo(); } int main(void) { make_foo(); return 0; }