Eventloop中维护了一个定时器队列:
boost::scoped_ptr timerQueue_;
在事件循环中的所有定时器事件都会被放入到这个”队列”中,当然本质上是放在一棵平衡二叉树中(muti-set)。放置定时器的相关函数主要是是Eventloop中的下列成员函数:
TimerId runAt(const Timestamp& time, const TimerCallback& cb); //在时间点time调用cb
TimerId runAfter(double delay, const TimerCallback& cb);//过delay的时间后调用cb
TimerId runEvery(double interval, const TimerCallback& cb);//每隔internal的时间调用一次cb
三个函数都包含两个参数,一个是时间相关的参数,一个是回调函数。都表示在某个时间(粗略)到达后,调用相应的回调函数。从它们的源码可以看出,核心是调用timerQueue_的成员函数addTimer:
timerQueue_->addTimer(cb, time, 0.0);
下面我们看一下timerQuene的实现:
const int timerfd_;
TimerList timers_;
其中timers_是一个multiset(平衡二叉树),树中的元素是Timer,Timer是定时器对象。它封装了定时器相关的事件和回调函数。timerfd_是一个文件描述符,它通过timerfd_create创建。timefd_是linux中的一种机制,通过它可以将定时器事件和IO事件统一管理。因此我们可以像普通的文件描述符一样将timefd加入到epoll中,不过它的事件触发机制和普通的文件描述符不同,它是到达一定的设置时间后就会触发一个事件,时间可以由timerfd_settime库函数进行设置。因此会想到,对TimerQuene里面多个定时器的管理可以简化为对timefd_的管理:我们只需要把timers_中定时时间最短的Timer的时间设置成timefd_的时间即可,这样就可以每次到时都执行定时时间最短的定时器。muduo中正是这样实现的。正如前面所说,TimerQuene里面的timerfdChannel_是timefd_和eventloop事件循环之间的桥梁。
我们回到最开始,对TimerQuene中的addTimer进行解析一下:
参数:cb:回调函数
参数:when :何时调用cb
参数 internal : 第一次调用cb后,每隔internal时间再调用cb
TimerId TimerQueue::addTimer(const TimerCallback& cb,
Timestamp when,
double interval)
{
Timer* timer = new Timer(cb, when, interval);//创建一个timer对象实例
loop_->runInLoop(
boost::bind(&TimerQueue::addTimerInLoop, this, timer)); //在当前TimerQuene所属的loop中调用addTimerLoop
return TimerId(timer, timer->sequence());
}
该函数的核心是调用addTimerInLoop函数:
void TimerQueue::addTimerInLoop(Timer* timer)
{
loop_->assertInLoopThread();
bool earliestChanged = insert(timer); //将timmer添加到当前的TimerQuene中
//如果当前加入的timmer到时时间比原先TimerQuene中的所有定时器到时时间都短,则需要更新timefd_
if (earliestChanged)
{
resetTimerfd(timerfd_, timer->expiration()); //更新timefd_
}
}
insert函数是这里的核心:
bool TimerQueue::insert(Timer* timer)
{
loop_->assertInLoopThread();
assert(timers_.size() == activeTimers_.size());
bool earliestChanged = false;
//获取这个timer的到时时间
Timestamp when = timer->expiration();
TimerList::iterator it = timers_.begin();
//如果当前的定时器二叉树times_为空 或 里面最邻近到时的定时器的到时时间比timer久,则设置earliestChanged
if (it == timers_.end() || when < it->first)
{
earliestChanged = true;
}
{
//将timer加入到timers中,timers是一个set,它里面的元素按大小顺序存放
std::pair::iterator, bool> result
= timers_.insert(Entry(when, timer));
assert(result.second); (void)result;
}
{
//将timer加入到ActiveTimerset中
std::pair::iterator, bool> result
= activeTimers_.insert(ActiveTimer(timer, timer->sequence()));
assert(result.second); (void)result;
}
assert(timers_.size() == activeTimers_.size());
return earliestChanged; //如果新加入的timer到时时间最近,则earliestChanged为true,反则为false
}
根据源码注释可以很好理解。我们继续回到上面的addTimerInLoop函数。接下来它会根据earliestChanged设置timefd_,这个不久赘述了。
接下来看一下定时器到时后的处理流程:
在TimeQuene构造函数中:
timerfdChannel_.setReadCallback(
boost::bind(&TimerQueue::handleRead, this));
// we are always reading the timerfd, we disarm it with timerfd_settime.
timerfdChannel_.enableReading();
因此定时器到时时会调用handleRead函数,这是所有定时器处理器的公共入口。
void TimerQueue::handleRead()
{
loop_->assertInLoopThread();
//得到当前的时间
Timestamp now(Timestamp::now());
readTimerfd(timerfd_, now);
//获取在这个时间点到时的所有定时器,并把他们存储在expired数组中
//同时这个函数也会把这些定时器从timers_中删除掉
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)
{
//一次调用每个定时器的run函数,run函数里面会调用相应定时器设置的回调函数。
it->second->run();
}
callingExpiredTimers_ = false;
//因为可能有些定时器需要在一定间隔内循环触发,这里会刷新一遍定时器队列
reset(expired, now);
}
这个函数核心就是处理定时器事件。我们还记得在设置定时器的时候,有一个参数叫做internal,这个参数的意思是需要每个internal的时间都调用一次定时器处理函数。对于这样的定时器,我们不能调用一次后直接把它删掉,因此需要重新把它设置到timers_中,这就是最后一行语句reset(expired, now);的作用,当然它还负责重新设置timefd_:
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());
//如果这个定时器是需要重复启动的,即internal>0
if (it->second->repeat()
&& cancelingTimers_.find(timer) == cancelingTimers_.end())
{
it->second->restart(now);//则重启定时器,其实就是重新设置对应timer的到时时间:expiration_
insert(it->second); //把定时器重新加到Timequene中
}
else
{
// FIXME move to a free list
delete it->second; // 否则就将定时器删除掉,记住expired中的所有定时器已经移出timers_了
}
}
//找到最近到时的定时器,并重新设置timefd
if (!timers_.empty())
{
nextExpire = timers_.begin()->second->expiration();
}
if (nextExpire.valid())
{
resetTimerfd(timerfd_, nextExpire);
}
}
到这里定时器的内容就OK了,后面我们将学习muduo怎么处理IO事件中的读写,关键是缓冲区的设置。