TimeQueue定时器
图片转载自:muduo网络库源码解析(4):TimerQueue定时机制_李兆龙的技术博客_51CTO博客
添加新的定时器
TimerId TimerQueue::addTimer(TimerCallback cb, //用户自定义回调
Timestamp when, //定时器的超时时刻
double interval) //重复触发间隔,小于0则不重复触发
{
Timer* timer = new Timer(std::move(cb), when, interval); //回调函数交由timer保管
loop_->runInLoop(
std::bind(&TimerQueue::addTimerInLoop, this, timer));
return TimerId(timer, timer->sequence());
}
通过addTImer添加一个定时器(实际调用的是addTimerInLoop),在runInLoop中注册一个函数addTimerInLoop,
之后在EventLoop的runInLoop中会把该函数放入pedingFunctor中,在loop中调用执行.
当然runInloop并不只有这么简单.
删除定时器
void TimerQueue::cancel(TimerId timerId)
{
loop_->runInLoop(
std::bind(&TimerQueue::cancelInLoop, this, timerId));
}
也是一样把实际做删除操作的函数放入pedingFunctor,在loop中调用执行
TimerQueue::TimerQueue(EventLoop* loop)
: loop_(loop), //当前TimeQueue属于某个EventLoop
timerfd_(createTimerfd()), //初始化一个timefd
timerfdChannel_(loop, timerfd_), //初始化一个关注timerfd的channel
timers_(), //保存所有的定时器
callingExpiredTimers_(false)
{
timerfdChannel_.setReadCallback(
std::bind(&TimerQueue::handleRead, this)); //为timerfd注册读回调
// we are always reading the timerfd, we disarm it with timerfd_settime.
timerfdChannel_.enableReading(); //使timerfd读可用
}
在构造函数中有一个timerfd,这是一个文件描述符,当它被设置的超时时间到了后变为可读,所以它可以被pool/epoll监听.
设置了超时后调用的回调函数 handleRead
timerfd可读后触发的回调函数
void TimerQueue::handleRead()
{
loop_->assertInLoopThread();
Timestamp now(Timestamp::now());//获取当前时间
readTimerfd(timerfd_, now); //读取超时时间,因为muduo默认LT 防止多次触发
std::vector<Entry> expired = getExpired(now); //获取目前超时的timer,并且移动给expired
callingExpiredTimers_ = true; //处理超时事件
/*
在处理超时事件时,如果超时的timer被删除了(也就是调用了cancelInLoop),那么为了保证超时回调正确执行,
会把它加入到待删除队列中,再在reset()函数中对待删除队列执行操作(cancelingTimers_)
*/
cancelingTimers_.clear(); //清空删除队列,这里面的timer已经在reset中被delete了
// safe to callback outside critical section
for (const Entry& it : expired)
{
it.second->run(); //通过timer调用用户传入的超时回调
}
callingExpiredTimers_ = false; //超时事件处理完成
reset(expired, now);
}
在handleRead()中,通过getExpired()读取到了所有超时的定时器并返回vector维护,之后依次处理这些超时的定时器,并且调用这些超时定时器的回调
可以看到 callingExpiredTimers_ 在处理超时事时被设置为了 true 处理完成后则设置为 false
因为在处理超时事件时,定时器已经被移动到了expired中,如果此时在对其进行删除,在timers_和activeTimers_中
是找不到的,所以通过cancelingTimers_保存这些定时器,等到超时事件处理完成再去reset()中处理.
取消定时器实际调用cancelInLoop
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)); //在timers_中删除定时器
assert(n == 1); (void)n;
delete it->first; /*在activeTimers_中删除定时器(应为是裸指针,所以delete,
并且timers_和activeTimers_中timer_是同一个指针)*/
activeTimers_.erase(it);
}
else if (callingExpiredTimers_) //判断是否正在处理超时事件
{
cancelingTimers_.insert(timer);
}
assert(timers_.size() == activeTimers_.size());
}
添加定时器的实际调用
void TimerQueue::addTimerInLoop(Timer* timer)
{
loop_->assertInLoopThread(); //判断是否在IO线程中
bool earliestChanged = insert(timer); //插入Timers_和activeTimers_(cancel)
if (earliestChanged)
{
//如果timers中最低的时间限度被更新,就更新一次超时时刻
resetTimerfd(timerfd_, timer->expiration());
}
}
bool TimerQueue::insert(Timer* timer)
{
loop_->assertInLoopThread();
assert(timers_.size() == activeTimers_.size());
bool earliestChanged = false;
Timestamp when = timer->expiration(); //获取传入的timer的超时时刻
/*获取队列中定时时间最短的项,即第一个 因为数据结构是set,红黑树有序,
比较顺序为pair的比较顺序 即先比较first,相同比较second*/
TimerList::iterator it = timers_.begin();
if (it == timers_.end() || when < it->first)//timers中不存在任何的Timer或者传入的timer超时时刻小于最小的那一个
{
earliestChanged = true; //刚刚插入的timer是定时器队列中的超时时刻最短时间内被触发的
}
{
std::pair<TimerList::iterator, bool> result
= timers_.insert(Entry(when, timer));//插入set容器, when:超时时间, timer:定时器
//就算Timestamp(超时时间)一样后面的地址(timer,这是一个指针)也一定不一样
assert(result.second); (void)result; //断言,永真
}
{
//插入activeTimers_容器
std::pair<ActiveTimerSet::iterator, bool> result
= activeTimers_.insert(ActiveTimer(timer, timer->sequence()));
assert(result.second); (void)result;
}
assert(timers_.size() == activeTimers_.size()); //两个容器中的定时器数量要一致
return earliestChanged; //是否把timer插入到set中
}
获取超时定时器getExpired()
std::vector<TimerQueue::Entry> TimerQueue::getExpired(Timestamp now)
{
assert(timers_.size() == activeTimers_.size());
std::vector<Entry> expired;
//UINTPTR_MAX为 uintptr_t 类型对象的最大值
//“reinterpret_cast 运算符并不会改变括号中运算对象的值,而是对该对象从位模式上进行重新解释”
Entry sentry(now, reinterpret_cast<Timer*>(UINTPTR_MAX));
// 返回第一个未到期的Timer的迭代器,因为set是有序的,所以在它前面的tiemr都是到期的
// lower_bound的含义是返回第一个值>=sentry的元素的iterator
// 即*end >= sentry,从而end->first > now
// 注意:此处是>,而不是>=
TimerList::iterator end = timers_.lower_bound(sentry);
assert(end == timers_.end() || now < end->first);
//将[being(),end)之间的元素(到期的)追加到expired的末尾
std::copy(timers_.begin(), end, back_inserter(expired));
timers_.erase(timers_.begin(), end); //删除刚刚复制的定时器
for (const Entry& it : expired)
{
// 从activeTimers_中移除到期的定时器
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;
}
reset函数
void TimerQueue::reset(const std::vector<Entry>& expired, Timestamp now)
{
Timestamp nextExpire;
for (const Entry& it : expired)
{
ActiveTimer timer(it.second, it.second->sequence());//timer sequence
if (it.second->repeat() //有设置超时时间
&& cancelingTimers_.find(timer) == cancelingTimers_.end()) //超时的timer,不在待删除队列中
{
it.second->restart(now); //重置超时时间
insert(it.second); //重新加入timers_ 和 activeTimers_监听超时事件
}
else //没有设置超时时间,或者刚刚超时的timer在执行超时回调时被加入了删除队列
{
// FIXME move to a free list
delete it.second; // FIXME: no delete please 删除timer指针
}
}
if (!timers_.empty())
{
nextExpire = timers_.begin()->second->expiration(); //最小的期望时间数
}
if (nextExpire.valid()) //最小的时间项数有效的话
{
resetTimerfd(timerfd_, nextExpire); //重置定时器
}
}
resetTimerfd()函数
// 重置定时器的超时时间,用到了timerfd_settime() expiration:用户定时器的超时时间
void resetTimerfd(int timerfd, Timestamp expiration)
{
// wake up loop by timerfd_settime()
struct itimerspec newValue;
struct itimerspec oldValue;
memZero(&newValue, sizeof newValue);
memZero(&oldValue, sizeof oldValue);
newValue.it_value = howMuchTimeFromNow(expiration); //设置超时时间,未设置it_interva,定时器定时一次
int ret = ::timerfd_settime(timerfd, 0, &newValue, &oldValue);//重新设置timerfd的超时时刻
if (ret)
{
LOG_SYSERR << "timerfd_settime()";
}
}
用户传入的timer被保存在timers_中,设置一个timerfd和对应的channel,它超时后调用handleread(),在这里面获取所有超时的定时器,并且依次执行这些定时器的回调函数(用户传入),那么这个**timerfdChannel__**就应该时常被触发
找到timerfd固定的超时时间
在addTimerInLoop()中,如果新加入的定时器的超时时刻,在定时器队列中是最小的那个,那么把新加入的超时时刻与之前timerfd的超时时刻取差值 作为timefd_的新的超时时刻