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属于某个EventLooptimerfd_(createTimerfd()), //初始化一个timefdtimerfdChannel_(loop, timerfd_), //初始化一个关注timerfd的channeltimers_(), //保存所有的定时器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,并且移动给expiredcallingExpiredTimers_ = true; //处理超时事件/*在处理超时事件时,如果超时的timer被删除了(也就是调用了cancelInLoop),那么为了保证超时回调正确执行,会把它加入到待删除队列中,再在reset()函数中对待删除队列执行操作(cancelingTimers_)*/cancelingTimers_.clear(); //清空删除队列,这里面的timer已经在reset中被delete了// safe to callback outside critical sectionfor (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 sequenceif (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 listdelete 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_的新的超时时刻















