muduo网络库学习—TimerQueue定时器(3)

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进行管理定时器,既有序,又方便插入删除。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值