一、类关系图
TimerId :表示一个定时器的整体,有定时器和序列号。
Timer:就是一个定时器的封装。
TimerQueue:定时器的处理流程封装。
二、时序图
三、定时器的使用
TimeQueue被封装在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);
)
这里讲解下添加定时器,定时器到期,以及取消定时器的流程。
3.1 添加定时器
TimerQueue->addTimer:
//cb 定时器到期执行函数,when 定时器到期时间,interval非零表示重复定时器
TimerId TimerQueue::addTimer(const TimerCallback& cb,
Timestamp when,
double interval)
{
//实例化一个定期器类
Timer* timer = new Timer(cb, when, interval);
//将addTimerInLoop方法放到EventLoop中执行,若用户在当前IO线程,回调则同步执行,否则将方法加入到队列,
//待IO线程被唤醒之后再来调用此回调。
loop_->runInLoop(boost::bind(&TimerQueue::addTimerInLoop, this, timer));
//实例化一个定时器和序列号封装的TimerId进行返回,用于用户取消定时器
return TimerId(timer, timer->sequence());
}
TimerQueue->addTimerInLoop:
void TimerQueue::addTimerInLoop(Timer* timer)
{
//判断是否在当前线程中
loop_->assertInLoopThread();
//插入一个定时器,并返回新添加的定时器是不是比队列里已存在的所有定时器过期时间还早
bool earliestChanged = insert(timer);
if (earliestChanged)
{
//如果新入队的定时器是队列里最早的,从新设置下系统定时器到期触发时间
resetTimerfd(timerfd_, timer->expiration());
}
}
TimerQueue->insert:
bool TimerQueue::insert(Timer* timer)
{
//检查是否在当前线程
loop_->assertInLoopThread();
//timers_和activeTimers_存着同样的定时器列表,个数是一样的
assert(timers_.size() == activeTimers_.size());
//timers_是按过期时间有序排列的,最早到期的在前面
//新插入的时间和队列中最早到期时间比,判断新插入时间是否更早
bool earliestChanged = false;
Timestamp when = timer->expiration();
TimerList::iterator it = timers_.begin();
if (it == timers_.end() || when < it->first)
{
earliestChanged = true;
}
//将时间保存入队,std::set自动保存有序
{
std::pair<TimerList::iterator, bool> result
= timers_.insert(Entry(when, timer));
assert(result.second); (void)result;
}
{
std::pair<ActiveTimerSet::iterator, bool> result
= activeTimers_.insert(ActiveTimer(timer, timer->sequence()));
assert(result.second); (void)result;
}
assert(timers_.size() == activeTimers_.size());
return earliestChanged;
}
Muduo->Net->Detail->resetTimerfd:
//此方法很简单,就是调用linux系统函数,重新设置下到期时间
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()";
}
}
3.2 定时器到期
TimerQueue->handleRead:
//TimerQueue实例化时设置好定时器回调。这里用到了Muduo通道Channel方法。
TimerQueue::TimerQueue(EventLoop* loop)
: loop_(loop),
timerfd_(createTimerfd()),
timerfdChannel_(loop, timerfd_),
timers_(),
callingExpiredTimers_(false)
{
timerfdChannel_.setReadCallback(
boost::bind(&TimerQueue::handleRead, this));
// we are always reading the timerfd, we disarm it with timerfd_settime.
timerfdChannel_.enableReading();
}
void TimerQueue::handleRead()
{
loop_->assertInLoopThread();
Timestamp now(Timestamp::now());
//定时器到期,读取一次句柄
readTimerfd(timerfd_, now);
//获取所有到期的时间
std::vector<Entry> expired = getExpired(now);
//处于定时器处理状态中
callingExpiredTimers_ = true;
cancelingTimers_.clear();
//执行每个到期的定时器方法
// safe to callback outside critical section
for (std::vector<Entry>::iterator it = expired.begin();
it != expired.end(); ++it)
{
it->second->run();
}
callingExpiredTimers_ = false;
//重置过期定时器状态,如果是重复执行定时器就再入队,否则删除
reset(expired, now);
}
TimerQueue->getExpired:
//获取now之前的过期定时器
std::vector<TimerQueue::Entry> TimerQueue::getExpired(Timestamp now)
{
assert(timers_.size() == activeTimers_.size());
std::vector<Entry> expired;
Entry sentry(now, reinterpret_cast<Timer*>(UINTPTR_MAX));
//这里用到了std::set::lower_bound(寻找第一个大于等于Value的值),
//这里的Timer*是UINTPTR_MAX,所以返回的是一个大于Value的值,即第一个未到期的Timer的迭代器
TimerList::iterator end = timers_.lower_bound(sentry);
//end是一个大于sentry的值,所以是<判断。
assert(end == timers_.end() || now < end->first);
//拷贝出过期的定时器
std::copy(timers_.begin(), end, back_inserter(expired));
//从已有队列中清楚
timers_.erase(timers_.begin(), end);
for (std::vector<Entry>::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;
}
这里getExpired返回的是一个列表,而我们设置系统过期时间是定时器队列里面最早的时间,如果所有的定时器时间都不一样,
则getExpired返回的列表中永远都只有一个元素。但是我们可以设置多个定时器都具有相同的过期时间,那么getExpired返回的列表中是具有相同过期时间的定时器。
TimerQueue->reset:
void TimerQueue::reset(const std::vector<Entry>& expired, Timestamp now)
{
Timestamp nextExpire;
for (std::vector<Entry>::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())
{
//1、定时器是重复定时器,2、取消定时器队列中无此定时器。
//则此定时器再入队
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())
{
//重新设置下系统定时器时间
resetTimerfd(timerfd_, nextExpire);
}
}
3.3 取消定时器
TimerQueue->cancel:
void TimerQueue::cancel(TimerId timerId)
{
loop_->runInLoop(
boost::bind(&TimerQueue::cancelInLoop, this, timerId));
}
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;
delete it->first; // FIXME: no delete please
activeTimers_.erase(it);
}
else if (callingExpiredTimers_)
{
//callingExpiredTimers_为true表示过期定时触发,正在handleRead中处理过期时间
//将要取消的定时器放入取消定时器队列,TimerQueue->reset会用到。
cancelingTimers_.insert(timer);
}
assert(timers_.size() == activeTimers_.size());
}
TimerQueue->cancelInLoop方法中cancelingTimers_和callingExpiredTimers_是为了应对“自注销”这种情况。
举例:
muduo::EventLoop*g_loop;
muduo::TimerId toCancel;
void cancelSelf()
{
print("cancelSelf()");
g_loop->cancel(toCancel);
}
int main()
{
muduo::EventLoop loop;
g_loop = &loop;
toCancel = loop.runEvery(5, cancelSelf);
loop.loop()
}
当运行到上例的g_loop->cancel时,toCancel代表的Timer已经不在timers_和activeTimers_这两个容器中,而是位于getExpired方法中的expired中。之所以要保存取消的定时器,上文方法cancelInLoop也已说明,是为了给重置定时器的方法使用。在重置定时器期间(即reset方法),如果有取消定时器行为,TimerQueue会记住在本次调用到期Timer期间有哪些cancel()请求,并且不再把已cancel()的Timer添加到timers_和activeTimers_当中。
三、体会
- sleep()、alarm()、usleep()在实现时有可能用了SIGALRM信号,在多线程程序中处理信号是个相当麻烦的事情,应当尽量避免。
- linux timerfd_create()把时间变成了一个文件描述符,该“文件”在定时器超时那一刻变得可读,这样就能很方便的融入select()、poll()框架中,用统一的方式来处理IO事件和超时事件。
- 对于不支持timerfd_create()的环境,可以考虑起一个线程来模拟timerfd功能,到期往特定描述符写入一定数据。这样可以达到统一管理文件描述符的方式来编码。
- rvo优化。
- 另外一点就是多体会Muduo编码思想。