常见的与时间相关的函数有:sleep,alarm,usleep,nanosleep,clock_nanosleep,gettimer/settitimer,timer_create/timer_settime/timer_gettime/timer_delete,还有muduo使用的timerfd_create/timerfd_gettime/timerfd_settime函数。
为什么选择timerfd_*函数呢?
>>>sleep/alarm/usleep在实现时有可能用了信号SIGALRM,在多线程程序中处理信号是个相当麻烦的事情,应该尽量避免。
>>>nanosleep和clock_nanosleep是线程安全的,但是在非阻塞网络编程中,绝对不能用让线程挂起的方式来等待一段时间。程序会失去响应,正确的做法是注册一个时间回调函数。
>>>getitimer和timer_create也是用信号来deliver超时,在多线程程序中也会有麻烦。
>>>timer_create可以指定信号的接收方是进程还是线程,算是一个进步,不过在信号处理函数(signal handler)中能做的事情实在是很受限。
>>>timerfd_create把时间变成了一个文件描述符,该“文件”在定时器超时的那一刻变得可读,这样就能很方便的融入到select/poll框架中,用统一的方式来处理I/O和超时事件,这正是Reactor模式的长处。实际上如果非要处理信号的话,使用signalfd将信号转变成文件描述符处理。
比较:libevent定时器采用本地unix域通信,当时间最小堆中超时事件发生后,写一个字节给本地socket,使得描述符可读,epoll检测到并处理该事件。
首先看timerfd_create()函数:
int timerfd_create(int clockid, int flags);
1.参数1的clockid是指,CLOCK_REALTIME or CLOCK_MONOTONIC时间。前者是我们平时所看到的时间,后者是石英钟时间也就是晶振决定的时间。所以假如我们制订了3分钟的定时,如果我们直接修改时间到三分钟后,那么前者会触发,而后者不会触发,它是晶振决定的相对时间,不取决于外部更改。所以一般定时器采用后者
2.参数2有两个选项TFD_NONBLOCK和TFD_CLOEXEC,这个就不用解释了。
第二个函数是timerfd_settime()函数
int timerfd_settime(int fd, int flags,
const struct itimerspec *new_value,
struct itimerspec *old_value);
1.参数1是文件描述符。
2.参数2是参加timerfd_create()函数。
3.参数3是新值,itermerspec结构体有两个字段,解释见下面。
4.参数4是旧值,结构体见下面。
struct timespec {new_value.it_value to a nonzero value arms the timer. Setting both fields of new_value.it_value to zero disarms the timer.
mudo的定时器由三个类实现,TimerId,Timer,TimerQueue,用户只能看到第一个类,其它两个类都是内部实现细节。
(1)TimerId被设计用来取消Timer的,它的结构很简单,只有一个Timer指针和其序列号。其中还声明了TimerQueue为其友元,可以操作其私有数据。
它的实现如下:
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_;
};
(2)Timer类
Timer是对定时器的高层次抽象,封装了定时器的一些参数,例如超时回调函数、超时时间、超时时间间隔、定时器是否重复、定时器的序列号。其函数大都是设置这些参数,run()用来调用回调函数,restart()用来重启定时器(如果设置为重复)。
主要实现:
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()) //先加后获取,由于初始值s_numCreated为0,所以序号这里从1开始
{ }
#ifdef __GXX_EXPERIMENTAL_CXX0X__
Timer(TimerCallback&& cb, Timestamp when, double interval)
: callback_(std::move(cb)),
expiration_(when),
interval_(interval),
repeat_(interval > 0.0),
sequence_(s_numCreated_.incrementAndGet())
{ }
#endif
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_; //定时器回调函数,TimerCallback在callback.h文件中定义,类型void()
Timestamp expiration_; //下一次的超时时刻
const double interval_; //超时时间间隔,如果是一次定时器,该值为0
const bool repeat_; //是否重复
const int64_t sequence_; //定时器序号
static AtomicInt64 s_numCreated_; //定时器计数,当前已创建的定时器数量,原子int64_t类型,初始值为0
};
.cpp:
#include
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(); //获取一个无效事件戳,即值为0
}
}
(3)TimerQueue数据结构的选择
TimerQueue的接口很简单,只有两个函数addTimer()和cancel()。它的内部有channel,和timerfd相关联。添加新的Timer后,在超时后,timerfd可读,会处理channel事件,之后调用Timer的回调函数;在timerfd的事件处理后,还有检查一遍超时定时器,如果其属性为重复还有再次添加到定时器集合中。
时序图:
typedef std::pair Entry;
typedef std::set TimerList;
typedef std::pair ActiveTimer;
typedef std::set ActiveTimerSet;
看它的头文件:
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);
#ifdef __GXX_EXPERIMENTAL_CXX0X__
TimerId addTimer(TimerCallback&& cb,
Timestamp when,
double interval);
#endif
void cancel(TimerId timerId); //可以跨线程调用
private:
// FIXME: use unique_ptr instead of raw pointers.
//下面两个set可以说保存的是相同的东西,都是定时器,只不过排序方式不同
typedef std::pair Entry; //set的key,是一个时间戳和定时器地址的pair
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(); //定时器事件产生回调函数
// move out all expired timers
std::vector getExpired(Timestamp now); //返回超时的定时器列表
void reset(const std::vector& expired, Timestamp now); //对超时的定时器进行重置,因为超时的定时器可能是重复的定时器
bool insert(Timer* timer); //插入定时器
EventLoop* loop_; //所属的event_loop
const int timerfd_; //就是timefd_create()所创建的定时器描述符?
Channel timerfdChannel_; //这是定时器事件的通道
// Timer list sorted by expiration
TimerList timers_; //定时器set,按时间戳排序
// for cancel()
ActiveTimerSet activeTimers_; //活跃定时器列表,按定时器地址排序
bool callingExpiredTimers_; /* atomic */ //是否处于调用处理超时定时器当中
ActiveTimerSet cancelingTimers_; //保存的是被取消的定时器
};
实现文件:
#ifndef __STDC_LIMIT_MACROS
#define __STDC_LIMIT_MACROS
#endif
#include
#include
#include
#include
#include
#include
#include
namespace muduo
{
namespace net
{
namespace detail
{
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(); //超时时刻微秒数-当前时间微秒数
if (microseconds < 100) //不能小于100,精确度不需要
{
microseconds = 100;
}
struct timespec ts; //转换成这个结构体返回
ts.tv_sec = static_cast(
microseconds / Timestamp::kMicroSecondsPerSecond);
ts.tv_nsec = static_cast(
(microseconds % Timestamp::kMicroSecondsPerSecond) * 1000);
return ts;
}
//从timerfd读取,避免定时器事件一直触发
void readTimerfd(int timerfd, Timestamp now)
{
uint64_t howmany;
ssize_t n = ::read(timerfd, &howmany, sizeof howmany); //从timerfd读取4个字节,这样timerfd就不会一直触发了
LOG_TRACE << "TimerQueue::handleRead() " << howmany << " at " << now.toString();
if (n != sizeof howmany)
{
LOG_ERROR << "TimerQueue::handleRead() reads " << n << " bytes instead of 8";
}
}
//重置定时器超时时刻
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); //将时间戳类转换成it_value的形式
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_create,返回timerfd
timerfdChannel_(loop, timerfd_),
timers_(),
callingExpiredTimers_(false)
{
/*Channel timerfdChannel_; //这是定时器事件的通道*/
//设置定时器类型通道的读回调函数。
timerfdChannel_.setReadCallback(
boost::bind(&TimerQueue::handleRead, this));
// we are always reading the timerfd, we disarm it with timerfd_settime.
timerfdChannel_.enableReading(); //注册,底层是一系列update,你懂的。
}
TimerQueue::~TimerQueue()
{
timerfdChannel_.disableAll();
timerfdChannel_.remove();
::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; //析构函数只释放一次,因为两个set保存的是一样的
}
}
//增加一个定时器
TimerId TimerQueue::addTimer(const TimerCallback& cb,
Timestamp when,
double interval)
{
Timer* timer = new Timer(cb, when, interval); //构造一个定时器对象,interval>0就是重复定时器
loop_->runInLoop(
boost::bind(&TimerQueue::addTimerInLoop, this, timer));
return TimerId(timer, timer->sequence());
}
#ifdef __GXX_EXPERIMENTAL_CXX0X__
TimerId TimerQueue::addTimer(TimerCallback&& cb,
Timestamp when,
double interval)
{
Timer* timer = new Timer(std::move(cb), when, interval);
loop_->runInLoop(
boost::bind(&TimerQueue::addTimerInLoop, this, timer));
return TimerId(timer, timer->sequence());
}
#endif
//执行线程退出的回调函数
void TimerQueue::cancel(TimerId timerId)
{
loop_->runInLoop(
boost::bind(&TimerQueue::cancelInLoop, this, timerId));
}
void TimerQueue::addTimerInLoop(Timer* timer)
{
loop_->assertInLoopThread();
//插入一个定时器游客能会使得最早到期的定时器发生改变,比如当前插入一个最早到期的,那就要重置定时器超时时刻
bool earliestChanged = insert(timer);
if (earliestChanged) //如果改变了
{
resetTimerfd(timerfd_, timer->expiration()); //重置定时器超时时刻
}
}
void TimerQueue::cancelInLoop(TimerId timerId)
{
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;
//如果用unique_ptr这里就不需要手工删除了
delete it->first; // FIXME: no delete please
activeTimers_.erase(it);
}
else if (callingExpiredTimers_) //如果在定时器列表中没有找到,可能已经到期,且正在处理的定时器
{
//已经到期,并且正在调用回调函数的定时器
cancelingTimers_.insert(timer);
}
assert(timers_.size() == activeTimers_.size());
}
//可读事件处理
void TimerQueue::handleRead()
{
loop_->assertInLoopThread(); //断言I/O线程中调用
Timestamp now(Timestamp::now());
readTimerfd(timerfd_, now); //清除该事件,避免一直触发,实际上是对timerfd做了read
//获取该时刻之前所有的定时器列表,即超时定时器列表,因为实际上可能有多个定时器超时,存在定时器的时间设定是一样的这种情况
std::vector expired = getExpired(now);
callingExpiredTimers_ = true; //处于处理定时器状态中
cancelingTimers_.clear();
// safe to callback outside critical section
for (std::vector::iterator it = expired.begin();
it != expired.end(); ++it)
{
it->second->run(); //调用所有的run()函数,底层调用Timer类的设置了的超时回调函数
}
callingExpiredTimers_ = false;
reset(expired, now); //如果移除的不是一次性定时器,那么重新启动它们
}
//返回当前所有超时的定时器列表
//返回值由于rvo优化,不会拷贝构造vector,直接返回它
std::vector TimerQueue::getExpired(Timestamp now)
{
assert(timers_.size() == activeTimers_.size());
std::vector expired;
Entry sentry(now, reinterpret_cast(UINTPTR_MAX));//创建一个时间戳和定时器地址的集合
//返回第一个未到期的Timer的迭代器
//lower_bound返回第一个值>=sentry的
//即*end>=sentry,从而end->first > now,而不是>=now,因为pair比较的第一个相等后会比较第二个,而sentry的第二个是UINTPTR_MAX最大
//所以用lower_bound没有用upper_bound
TimerList::iterator end = timers_.lower_bound(sentry);
assert(end == timers_.end() || now < end->first); //now < end->first
std::copy(timers_.begin(), end, back_inserter(expired)); //将到期的定时器插入到expired中
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());
//如果是重复的定时器,并且是未取消定时器,则重启该定时器
if (it->second->repeat()
&& cancelingTimers_.find(timer) == cancelingTimers_.end())
{
it->second->restart(now); //restart()函数中会重新计算下一个超时时刻
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的超时时间
resetTimerfd(timerfd_, nextExpire);
}
}
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是set实现的,所以第一个就是最早,空的也算
if (it == timers_.end() || when < it->first)
{
earliestChanged = true; //如果插入定时器时间小于最早到期时间
}
//下面两个插入的set保存的是一样的,都是定时器,只不过对组的另一个辅助成员不一样
{
//利用RAII机制
//插入到timers_中,result是临时对象,需要用它来保证插入成功
std::pair result
= timers_.insert(Entry(when, timer));
assert(result.second); (void)result;
}
{
//插入到activeTimers中
std::pair result
= activeTimers_.insert(ActiveTimer(timer, timer->sequence()));
assert(result.second); (void)result;
}
assert(timers_.size() == activeTimers_.size());
return earliestChanged; //返回是否最早到期时间改变
}
主要是在EventLoop中使用,EventLoop中为我们提供了四个函数,供用户使用,我们来看一下:
/在时间戳为time的时间执行,0.0表示一次性不重复
TimerId EventLoop::runAt(const Timestamp& time, const TimerCallback& cb)
{
return timerQueue_->addTimer(cb, time, 0.0);
}
//延迟delay时间执行的定时器
TimerId EventLoop::runAfter(double delay, const TimerCallback& cb)
{
Timestamp time(addTime(Timestamp::now(), delay)); //合成一个时间戳
return runAt(time, cb);
}
//间隔性的定时器,起始就是重复定时器,间隔interval需要大于0
TimerId EventLoop::runEvery(double interval, const TimerCallback& cb)
{
Timestamp time(addTime(Timestamp::now(), interval));
return timerQueue_->addTimer(cb, time, interval);
}
//直接调用timerQueue的cancle
void EventLoop::cancel(TimerId timerId)
{
return timerQueue_->cancel(timerId);
}
而timerQueue是EventLoop类的成员:
boost::scoped_ptr timerQueue_;
所以用户操作这几个函数就可以利用定时器实现相应的功能。