muduo网络库学习—TimerQueue定时器(3)
`
文章目录
前言
Linux中由timerfd可以把时间变成一个文件描述符,该文件描述符在超时的那一刻变成可读的IO事件,因此我们可以用处理IO事件相同的方式处理定时,代码的一致性更好。
一、timerfd的使用
#include <sys/timerfd.h>
// 创建定时器
int timerfd_create(int clockid, int flags);
// 设置定时器
int timerfd_settime(int fd, int flags, const struct itimerspec *new_value,struct itimerspec *old_value);
// 获取定时器倒计时时间
int timerfd_gettime(int fd, struct itimerspec *curr_value);
函数timerfd_create:
生成一个定时器,返回关联的timerfd文件描述符。
参数clockid用于设定当前定时器时间类型:CLOCK_REALTIME为操作系统时间的,当用户修改系统时间时定时器触发时间将变化。 CLOCK_MONOTONIC可当做是相对时间,不会因系统时间变化而发生变化。
参数flags用于设定当前定时器文件描述符的性质,同socket fd,通常使用TFD_CLOEXEC 和 TFD_NONBLOCK。
函数timerfd_settime:
如果不需要重复促发直接将interval设置为0即可。
// 时间结构体
struct timespec {
time_t tv_sec; /* 秒*/
long tv_nsec; /* 纳秒*/
};
struct itimerspec {
struct timespec it_interval; /* 触发超时的间隔时间 /
struct timespec it_value; / 初始过期时间 */
};
二、TimerQueue定时器
muduo的定时器功能主要由三个class实现,TimerId,Timer,TimerQueue
用户只能看到第一个class,另外两个是内部实现细节。
1.Timer
Timer只是对定时操作的一个高层次的抽象,一个定时器主要由定时时刻到来时刻的回调函数,定时时刻以及是否重复执行,这三个要素构成。同时restart的函数是针对于已经到时的定时器,但是由于是重复执行的,所以重新设置到时时间。实现细节:这里注意静态成员变量类内声明类外初始化。
Timer.h代码如下:
#ifndef TIMER_H
#define TIMER_H
//封装定时器类
#include "Callbacks.h"
#include "../base/noncopyable.h"
#include "../base/Timestamp.h"
#include "../base/Atomic.h"
namespace muduo
{
namespace net{
class Timer{
public:
Timer(TimerCallback callback,muduo::Timestamp expirattion,const double interval):
callback_(callback),
expirattion_(expirattion),
interval_(interval),
repeat_(interval_>0),
sequence_(s_numCreated_.incrementAndGet()){
}
void run()const{
callback_();
}
Timestamp expirattion()const {
return expirattion_;
}
bool repeat()const{
return repeat_;
}
double interval()const{
return interval_;
}
int64_t sequence()const{
return sequence_;
}
static int64_t numCreated() {
return s_numCreated_.get();
}
void restart(Timestamp now);//开启定时器
private:
const TimerCallback callback_;//每个定时器对应的回调函数
muduo::Timestamp expirattion_;//每个定时器的到时时间,单位是us
const double interval_;//定时器重复的时间间隔,如果不重复设置位非正值
const bool repeat_;//定时器是否重复执行,interval_ s执行一次
const int64_t sequence_;//定时器的全局序号
static muduo::detail::AtomicInt64 s_numCreated_;
};
}
} // namespace muduo
#endif
Timer.cpp代码如下:
namespace muduo{
namespace net{
muduo::detail::AtomicInt64 Timer::s_numCreated_;//静态成员类外定义
void Timer::restart(Timestamp now){
if(repeat_){
//如果定时器重复则设置超时时间
expirattion_=muduo::addTime(now,interval_);
}
else{
//如果定时器不重复则设置一个非法的时间
expirattion_=Timestamp::invalid();
}
}
}
}
2.TimerQueue
TimerQueue是定时器管理的抽象,封装了timerfd的系统调用,将定时器队列的定时时间设置为整个定时器队列的最早到时时间,在添加定时器以及移除定时器的时候都需要进行调整。同时如何管理定时器,使其即能够快速查找又可以快速的进行插入和删除,这里muduo采用的时平衡二叉树,使用容器set,之所以没有选用map的原因是可能会出现定时时间相同的定时器。同时因为TimerQueue中的函数只能在所属的loop中调用所以不需要加锁,每个loop只有一个TimerQueue用于管理定时时间,在创建loop的同时TimerQueue已经创建好了,同时timerfd也已经加入poll中进行监听了。
构造函数代码如下:
TimerQueue::TimerQueue(Eventloop*loop):
loop_(loop),
timerfd_(detail::createTimerfd()),
timerfdChannel_(loop,timerfd_),
timers_(),
activeTimers_(),
cancleTimers_(),
callingExpiredTimers_(false)
{
timerfdChannel_.setReadCallBack(boost::bind(&TimerQueue::handleRead,this));
//添加对应事件到io复用中进行监听
timerfdChannel_.enableReading();
}
addtimer:添加定时器到队列中,可以跨线程调用(使用runinloop函数,具体实现见后文),返回一个TimerId对象。添加定时器后注意会不会影响timerQueue队列的到时时间,若影响需要进行更新!
TimerId TimerQueue::addTimer(const TimerCallback&cb,Timestamp when,double interval){
Timer*timer=new Timer(cb,when,interval);
loop_->runInLoop(
boost::bind(&TimerQueue::addTimerInloop, this, timer));
// addTimerInloop(timer);
return TimerId(timer,timer->sequence());
}
void TimerQueue::addTimerInloop(Timer*timer){
//断言在当前创建loop的线程中
loop_->assertInLoopThread();
// 插入一个定时器,有可能会使得最早到期的定时器发生改变
bool earliestChanged = insert(timer);
if (earliestChanged)
{
// 重置定时器的超时时刻(timerfd_settime)
detail::resetTimerfd(timerfd_, timer->expirattion());
}
}
bool TimerQueue::insert(Timer*timer){
//断言只能在创建loop的线程中调用
loop_->assertInLoopThread();
assert(timers_.size()==activeTimers_.size());
//最早到期时间是否更改
bool earliestChanged=false;
Timestamp when=timer->expirattion();
TimerList::iterator it=timers_.begin();
if(it==timers_.end()||when < it->first){
earliestChanged=true;
}
{
//insert timers_
std::pair<TimerList::iterator,bool> result=timers_.insert(Entry(when,timer));
assert(result.second);
}
{
//insert activeTimers_
std::pair<ActiveTimerSet::iterator,bool> result=activeTimers_.insert(ActiveTimer(timer,timer->sequence()));
assert(result.second);
}
assert(timers_.size()==activeTimers_.size());
return earliestChanged;
}
struct timespec howMuchTimeFromNow(Timestamp when){
int64_t microsecconds=when.microSecondsSinceEpoch()-Timestamp::now().microSecondsSinceEpoch();
if (microsecconds < 100)
{
microsecconds = 100;
}
struct timespec temp;
temp.tv_sec=static_cast<size_t>(microsecconds/Timestamp::microseconds_seconds);
temp.tv_nsec=static_cast<size_t>((microsecconds%Timestamp::microseconds_seconds)*1000);
return temp;
}
void resetTimerfd(int timerfd, Timestamp expiration){
struct itimerspec newvalue;
struct itimerspec oldvalue;
memset(&newvalue,0,sizeof(newvalue));
memset(&oldvalue,0,sizeof(oldvalue));
newvalue.it_value=howMuchTimeFromNow(expiration);
int ret=timerfd_settime(timerfd,0,&newvalue,&oldvalue);
if (ret)
{
LOG_SYSERR << "timerfd_settime()";
}
}
handleread:timerQueue定时时间到达,那么从容器中取出已经到时的定时器回调各个定时器的回调函数,然后reset已经处理过的定时器,如果是重复促发的话那么重新插入计时器队列,最后在timers_容器中取出最早到达定时时间设置为timerqueue的定时时间,开始下一轮定时。
void TimerQueue::handleRead(){
loop_->assertInLoopThread();
Timestamp now=Timestamp::now();
detail::readTimerfd(timerfd_,now);//防止一直促发定时器
//取出超时链表
std::vector<Entry> List= getExpired(now);
//开始处理超时链表
callingExpiredTimers_=true;
cancleTimers_.clear();
for(std::vector<Entry>::iterator it=List.begin();it!=List.end();it++){
//执行回调函数
it->second->run();
}
callingExpiredTimers_=false;//处理完所有过期定时器
//进行重置
reset(List,now);
}
std::vector<TimerQueue::Entry> TimerQueue::getExpired(Timestamp now){
loop_->assertInLoopThread();
assert(timers_.size()==activeTimers_.size());
std::vector<Entry> expired;
Entry sentry(now,reinterpret_cast<Timer*>(UINTPTR_MAX));
TimerList::iterator end=timers_.lower_bound(sentry);
assert(end==timers_.end()||now<end->first);
//拷贝过期的定时器来expired
std::copy(timers_.begin(),end,back_inserter(expired));
//从timers_以及activeTimers_中删除过期的定时器
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);
}
assert(timers_.size()==activeTimers_.size());
return expired;
}
void TimerQueue::reset(const std::vector<Entry>&expired,Timestamp now){
//已经过期的定时器是否重置
for(std::vector<Entry>::const_iterator it=expired.begin();it!=expired.end();it++){
if(it->second->repeat()&&cancleTimers_.find(ActiveTimer(it->second,it->second->sequence()))==cancleTimers_.end()){
it->second->restart(now);//重新设置超时时间
insert(it->second);//插入定时器队列
}
else{
printf("delete\n");
delete it->second;
}
}
//设置下一轮定时时间
Timestamp next;
if(timers_.begin()!=timers_.end()){
next=timers_.begin()->first;
if(next.valid()){
detail::resetTimerfd(timerfd_,next);
}
}
}
cancle:将定时器从timerQueue中取消,如果我们可以直接在timers_容器中找到则直接删除,若找不到我们则要考虑是否目前正在处理该定时器,如果是则先将该定时器放入cancleTimers_中,待reset重置已处理定时器时,即使定时器时重复促发的,但是该定时器在cancleTimers_中,也要将该定时器进行删除不能重复添加。
void TimerQueue::cancle(TimerId timeId){
loop_->runInLoop(
boost::bind(&TimerQueue::cancleInLoop, this, timeId));
// cancleInLoop(timeId);
}
void TimerQueue::cancleInLoop(TimerId timerid){
//断言只能在创建eventloop线程中使用
loop_->assertInLoopThread();
assert(timers_.size()==activeTimers_.size());
ActiveTimer timer(timerid.timer_,timerid.sequence_);
ActiveTimerSet::iterator it=activeTimers_.find(timer);
//判断是否在activeTimers_或者timers_中
if(it!=activeTimers_.end()){
size_t n=timers_.erase(Entry(it->first->expirattion(),it->first));
assert(n==1);
delete it->first;
activeTimers_.erase(timer);
}
else if(callingExpiredTimers_){
//不在在activeTimers_或者timers_中,说明可能已经过期正在处理
cancleTimers_.insert(timer);
}
assert(timers_.size()==activeTimers_.size());
}
3.TimerId
用户可见类:只包含一个定时器指针以及定时器编号
#ifndef TIMERID_H
#define TIMERID_H
#include "../base/noncopyable.h"
#include "Timer.h"
//此类用来保证定时器的唯一性
namespace muduo{
namespace net{
//设计的目的是避免有两个定时器地址相同
class TimerId{
public:
TimerId()
: timer_(NULL),
sequence_(0){
}
TimerId(Timer*timer,int64_t sequence):
timer_(timer),
sequence_(sequence){
}
friend class TimerQueue;//保证TimerQueue可以访问TimerId的私有成员
public:
Timer* timer_;
int64_t sequence_;
};
}
}
#endif
3.EventLoop的更新
通过eventloop直接添加删除定时器
TimerId Eventloop::runAt(Timestamp when,const TimerCallback&callback){
return timerQueue_->addTimer(callback,when,0.0);
}
TimerId Eventloop::runAfter(double delay,const TimerCallback&callback){
Timestamp time=addTime(Timestamp::now(),delay);
return timerQueue_->addTimer(callback,time,0.0);
}
TimerId Eventloop::runEvery(double interval,const TimerCallback&callback){
return timerQueue_->addTimer(callback,Timestamp::now(),interval);
}
void Eventloop::cancle(TimerId timeid){
timerQueue_->cancle(timeid);
}
总结
每个线程中都会有一个定时器队列用来管理当前线程的定时器,定时器队列的操作主要由timerfd系统调用来完成,采用使用平衡二叉树的set进行管理定时器,既有序,又方便插入删除。