传统的Reactor通过控制select和poll的等待时间来实现定时,而现在在Linux中有了timerfd,我们可以用和处理IO事件相同的方式来处理定时,代码的一致性更好。
常见的定时函数有如下几种:
sleep
alarm
usleep
nanosleep
clock_nanosleep
getitimer / setitimer
timer_create / timer_settime / timer_gettime / timer_delete
timerfd_create / timerfd_gettime / timerfd_settime
我们之所以选择timerfd,是因为:
1.sleep / alarm / usleep 在实现时有可能用了信号 SIGALRM,在多线程程序中处理信号是个相当麻烦的事情,应当尽量避免。
2.nanosleep 和 clock_nanosleep 是线程安全的,但是在非阻塞网络编程中,绝对不能用让线程挂起的方式来等待一段时间,程序会失去响应。正确的做法是注册一个时间回调函数。
3.getitimer 和 timer_create 也是用信号来传递超时,在多线程程序中也会有麻烦。
4.timer_create 可以指定信号的接收方是进程还是线程,算是一个进步,不过在信号处理函数(signal handler)能做的事情实在很受限。
5.timerfd_create 把时间变成了一个文件描述符,该“文件”在定时器超时的那一刻变得可读,这样就能很方便地融入到 select/poll 框架中,用统一的方式来处理 IO 事件和超时事件,这也正是 Reactor 模式的长处。
timerfd相关函数介绍:
#include
/**
* 此函数用于创建一个定时器文件
* 参数clockid可以是CLOCK_MONOTONIC或者CLOCK_REALTIME
* 参数flags可以是0或者TFD_CLOEXEC/TFD_NONBLOCK
* 函数返回值是一个文件句柄fd
*/
int timerfd_create(int clockid, int flags);
/**
* 此函数用于设置新的超时时间,并开始计时
* 参数fd是timerfd_create返回的文件句柄
* 参数flags为TFD_TIMER_ABSTIME(1)代表设置的是绝对时间;为0代表相对时间
* 参数new_value为需要设置的超时和间隔时间
* 参数old_value为定时器这次设置之前的超时时间
* 函数返回0代表设置成功
*/
int timerfd_settime(int fd, int flags, const struct itimerspec *new_value, struct itimerspec *old_value);
/**
* 此函数用于获得定时器距离下次超时还剩下的时间
* 如果调用时定时器已经到期,并且该定时器处于循环模式
* 即设置超时时间时struct itimerspec::it_interval不为0
* 那么调用此函数之后定时器重新开始计时
*/
int timerfd_gettime(int fd, struct itimerspec *curr_value);
itimerspec结构体;
struct itimerspec {
struct timespec it_interval; // interval for periodic timer
struct timespec it_value; // initial expiration
};
struct timespec {
time_t tv_sec; // seconds
long tv_nsec; // nano-seconds
};
muduo的定时器功能由三个class实现,TimerId、Timer、TimerQueue,用户只能看到第一个class,另外两个都是内部实现细节。
TimerId被设计用来取消Timer的,它的结构很简单,只有一个Timer指针和其序列号。其中还声明了TimerQueue为其友元,可以操作其私有数据。
Timer是对定时器的高层次抽象,封装了定时器的一些参数,例如超时回调函数、超时时间、超时时间间隔、定时器是否重复、定时器的序列号。其函数大都是设置这些参数,run()用来调用回调函数,restart()用来重启定时器(如果设置为重复)。
重点介绍一下TimerQueue类。
TimerQueue的接口很简单,只有两个函数addTimer()和cancel()。它的内部有channel,和timerfd相关联。添加新的Timer后,在超时后,timerfd可读,会处理channel事件,之后调用Timer的回调函数;在timerfd的事件处理后,还会检查一遍超时定时器,如果其属性为重复还会再次添加到定时器集合中。
(1)TimerQueue数据结构的选择
TimerQueue需要高效地组织目前尚未到期的Timer,能快速地根据当前时间找到已经到期的Timer,也要能高效地添加和删除Timer。因而可以用二叉搜索树(例如std::set/std::map),把Timer按到期时间先后排好序,其操作的复杂度是O(logN),但我们使用时还要处理两个Timer到期时间相同的情况(map不支持key相同的情况),做法如下:
// 两种类型的set,一种按时间戳排序,一种按Timer的地址排序
// 实际上,这两个set保存的是相同的定时器列表
typedef std::pair Entry;
typedef std::set TimerList;
typedef std::pair ActiveTimer;
typedef std::set ActiveTimerSet;
(2)代码分析
文件名:TimerQueue.h
#ifndef MUDUO_NET_TIMERQUEUE_H
#define MUDUO_NET_TIMERQUEUE_H
#include
#include
#include
#include
#include
#include
#include
namespace muduo
{
namespace net
{
class EventLoop;
class Timer;
class TimerId;
class TimerQueue : boost::noncopyable
{
public:
TimerQueue(EventLoop* loop);
~TimerQueue();
// 一定是线程安全的,可以跨线程调用。通常情况下被其它线程调用。
TimerId addTimer(const TimerCallback& cb,
Timestamp when,
double interval);
void cancel(TimerId timerId);
private:
// FIXME: use unique_ptr instead of raw pointers.
// unique_ptr是C++ 11标准的一个独享所有权的智能指针
// 无法得到指向同一对象的两个unique_ptr指针
// 但可以进行移动构造与移动赋值操作,即所有权可以移动到另一个对象(而非拷贝构造)
typedef std::pair Entry;
typedef std::set TimerList;
typedef std::pair ActiveTimer;
typedef std::set ActiveTimerSet;
// 以下成员函数只可能在其所属的I/O线程中调用,因而不必加锁。
// 服务器性能杀手之一是锁竞争,所以要尽可能少用锁
void addTimerInLoop(Timer* timer);
void cancelInLoop(TimerId timerId);
// called when timerfd alarms
void handleRead();
// 返回超时的定时器列表
std::vector getExpired(Timestamp now);
void reset(const std::vector & expired, Timestamp now);
bool insert(Timer* timer);
EventLoop* loop_; // 所属的EventLoop
const int timerfd_;
Channel timerfdChannel_;
TimerList timers_; // timers_是按到期时间排序
// for cancel()
// timers_与activeTimers_保存的是相同的数据
// timers_是按到期时间排序,activeTimers_是按对象地址排序
ActiveTimerSet activeTimers_;
bool callingExpiredTimers_; // 是否正在处理超时事件
ActiveTimerSet cancelingTimers_; // 保存的是被取消的定时器
};
}
}
#endif //MUDUO_NET_TIMERQUEUE_H
文件名:TimerQueue.cc
#define __STDC_LIMIT_MACROS
#include
#include
#include
#include
#include
#include
#include
namespace muduo
{
namespace net
{
namespace detail
{
// 创建定时器,用到了timerfd_create()
int createTimerfd()
{
int timerfd = ::timerfd_create(CLOCK_MONOTONIC,
TFD_NONBLOCK | TFD_CLOEXEC);
if (timerfd < 0)
{
LOG_SYSFATAL << "Failed in timerfd_create";
}
return timerfd;
}
// 计算超时时刻与当前时间的时间差
struct timespec howMuchTimeFromNow(Timestamp when)
{
int64_t microseconds = when.microSecondsSinceEpoch()
- Timestamp::now().microSecondsSinceEpoch();
// 精确度没有设置那么高,所以小于100ms时都置为100
if (microseconds < 100)
{
microseconds = 100;
}
struct timespec ts;
ts.tv_sec = static_cast(
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; // 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";
}
}
// 重置定时器的超时时间,用到了timerfd_settime()
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()), // 创建timerfd
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(); // timerfd对应的channel监听事件为可读事件
}
// 析构函数
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; // 手动释放Timer*
}
}
// 添加新的定时器
TimerId TimerQueue::addTimer(const TimerCallback& cb,
Timestamp when,
double interval)
{
Timer* timer = new Timer(cb, when, interval);
addTimerInLoop(timer);
return TimerId(timer, timer->sequence());
}
// 取消定时器
void TimerQueue::cancel(TimerId timerId)
{
cancelInLoop(timerId);
}
// 添加定时器时实际调用了addTimerInLoop()
void TimerQueue::addTimerInLoop(Timer* timer)
{
loop_->assertInLoopThread();
// 插入一个定时器,有可能会使得最早到期的定时器发生改变
bool earliestChanged = insert(timer);
if (earliestChanged)
{
// 重置定时器的超时时刻(timerfd_settime)
resetTimerfd(timerfd_, timer->expiration());
}
}
// 取消定时器时实际调用了cancelInLoop()
void TimerQueue::cancelInLoop(TimerId timerId)
{
loop_->assertInLoopThread();
assert(timers_.size() == activeTimers_.size());
ActiveTimer timer(timerId.timer_, timerId.sequence_); // 要取消的定时器timer
// 查找该定时器
ActiveTimerSet::iterator it = activeTimers_.find(timer);
// 要取消的在当前激活的Timer集合中
if (it != activeTimers_.end())
{
size_t n = timers_.erase(Entry(it->first->expiration(), it->first)); // 从timers_中移除
assert(n == 1); (void)n;
delete it->first; // FIXME:如果用了unique_ptr,这里就不需要手动删除了
activeTimers_.erase(it); // 从activeTimers_中移除
}
// 如果正在执行超时定时器的回调函数,则加入到cancelingTimers集合中
else if (callingExpiredTimers_)
{
cancelingTimers_.insert(timer);
}
assert(timers_.size() == activeTimers_.size());
}
void TimerQueue::handleRead()
{
loop_->assertInLoopThread();
Timestamp now(Timestamp::now());
readTimerfd(timerfd_, now); // 读timerfd
// 获取该时刻之前所有的定时器列表(即超时定时器列表)
std::vector expired = getExpired(now);
callingExpiredTimers_ = true;
cancelingTimers_.clear();
for (std::vector ::iterator it = expired.begin();
it != expired.end(); ++it)
{
// 这里回调定时器处理函数
it->second->run();
}
callingExpiredTimers_ = false;
// 把重复的定时器重新加入到定时器中
reset(expired, now);
}
// rvo即Return Value Optimization
// 是一种编译器优化技术,可以把通过函数返回创建的临时对象给”去掉”
// 然后达到少调用拷贝构造的操作,从而提高性能
std::vector TimerQueue::getExpired(Timestamp now)
{
assert(timers_.size() == activeTimers_.size());
std::vector expired;
// UINTPTR_MAX表示最大的地址
Entry sentry(now, reinterpret_cast(UINTPTR_MAX));
// 返回第一个未到期的Timer的迭代器
// lower_bound的含义是返回第一个值>=sentry的元素的iterator
// 即*end >= sentry,从而end->first > now
// 注意:此处是>,而不是>=
TimerList::iterator end = timers_.lower_bound(sentry);
assert(end == timers_.end() || now < end->first);
// 将[begin end)区间的元素(到期的)追加到expired末尾
std::copy(timers_.begin(), end, back_inserter(expired));
// 从timers_中移除到期的定时器
timers_.erase(timers_.begin(), end);
// 从activeTimers_中移除到期的定时器
for (std::vector ::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;
}
assert(timers_.size() == activeTimers_.size());
return expired;
}
void TimerQueue::reset(const std::vector & expired, Timestamp now)
{
Timestamp nextExpire;
for (std::vector ::const_iterator it = expired.begin();
it != expired.end(); ++it)
{
ActiveTimer timer(it->second, it->second->sequence());
// 如果是重复的定时器并且不在cancelingTimers_集合中,则重启该定时器
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
}
}
if (!timers_.empty())
{
// 获取最早到期的定时器超时时间
nextExpire = timers_.begin()->second->expiration();
}
if (nextExpire.valid())
{
// 重置定时器的超时时刻(timerfd_settime)
resetTimerfd(timerfd_, nextExpire);
}
}
// 插入一个timer
bool TimerQueue::insert(Timer* timer)
{
loop_->assertInLoopThread();
assert(timers_.size() == activeTimers_.size());
bool earliestChanged = false;
Timestamp when = timer->expiration();
TimerList::iterator it = timers_.begin();
// 如果timers_为空或者when小于timers_中的最早到期时间
if (it == timers_.end() || when < it->first)
{
earliestChanged = true;
}
{
// 插入到timers_中
std::pairbool> result
= timers_.insert(Entry(when, timer));
assert(result.second); (void)result;
}
{
// 插入到activeTimers_中
std::pairbool> result
= activeTimers_.insert(ActiveTimer(timer, timer->sequence()));
assert(result.second); (void)result;
}
assert(timers_.size() == activeTimers_.size());
return earliestChanged;
}