博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
muduo中定时器的管理
阅读量:4167 次
发布时间:2019-05-26

本文共 4756 字,大约阅读时间需要 15 分钟。

  Eventloop中维护了一个定时器队列:

boost::scoped_ptr
timerQueue_;

在事件循环中的所有定时器事件都会被放入到这个”队列”中,当然本质上是放在一棵平衡二叉树中(muti-set)。放置定时器的相关函数主要是是Eventloop中的下列成员函数:

TimerId runAt(const Timestamp& time, const TimerCallback& cb); //在时间点time调用cbTimerId runAfter(double delay, const TimerCallback& cb);//过delay的时间后调用cbTimerId runEvery(double interval, const TimerCallback& cb);//每隔internal的时间调用一次cb

三个函数都包含两个参数,一个是时间相关的参数,一个是回调函数。都表示在某个时间(粗略)到达后,调用相应的回调函数。从它们的源码可以看出,核心是调用timerQueue_的成员函数addTimer:

timerQueue_->addTimer(cb, time, 0.0);

下面我们看一下timerQuene的实现:

这里写图片描述
TimesQuene的核心数据成员是:

const int timerfd_;TimerList timers_;

其中timers_是一个multiset(平衡二叉树),树中的元素是Timer,Timer是定时器对象。它封装了定时器相关的事件和回调函数。timerfd_是一个文件描述符,它通过timerfd_create创建。timefd_是linux中的一种机制,通过它可以将定时器事件和IO事件统一管理。因此我们可以像普通的文件描述符一样将timefd加入到epoll中,不过它的事件触发机制和普通的文件描述符不同,它是到达一定的设置时间后就会触发一个事件,时间可以由timerfd_settime库函数进行设置。因此会想到,对TimerQuene里面多个定时器的管理可以简化为对timefd_的管理:我们只需要把timers_中定时时间最短的Timer的时间设置成timefd_的时间即可,这样就可以每次到时都执行定时时间最短的定时器。muduo中正是这样实现的。正如前面所说,TimerQuene里面的timerfdChannel_是timefd_和eventloop事件循环之间的桥梁。

  我们回到最开始,对TimerQuene中的addTimer进行解析一下:

参数:cb:回调函数参数:when :何时调用cb参数 internal : 第一次调用cb后,每隔internal时间再调用cbTimerId TimerQueue::addTimer(const TimerCallback& cb,                             Timestamp when,                             double interval){  Timer* timer = new Timer(cb, when, interval);//创建一个timer对象实例  loop_->runInLoop(      boost::bind(&TimerQueue::addTimerInLoop, this, timer)); //在当前TimerQuene所属的loop中调用addTimerLoop  return TimerId(timer, timer->sequence());}

该函数的核心是调用addTimerInLoop函数:

void TimerQueue::addTimerInLoop(Timer* timer){  loop_->assertInLoopThread();  bool earliestChanged = insert(timer); //将timmer添加到当前的TimerQuene中//如果当前加入的timmer到时时间比原先TimerQuene中的所有定时器到时时间都短,则需要更新timefd_  if (earliestChanged)   {    resetTimerfd(timerfd_, timer->expiration()); //更新timefd_  }}

insert函数是这里的核心:

bool TimerQueue::insert(Timer* timer){  loop_->assertInLoopThread();  assert(timers_.size() == activeTimers_.size());  bool earliestChanged = false;  //获取这个timer的到时时间  Timestamp when = timer->expiration();  TimerList::iterator it = timers_.begin();  //如果当前的定时器二叉树times_为空 或 里面最邻近到时的定时器的到时时间比timer久,则设置earliestChanged  if (it == timers_.end() || when < it->first)  {    earliestChanged = true;  }  {    //将timer加入到timers中,timers是一个set,它里面的元素按大小顺序存放    std::pair
result = timers_.insert(Entry(when, timer)); assert(result.second); (void)result; } { //将timer加入到ActiveTimerset中 std::pair
result = activeTimers_.insert(ActiveTimer(timer, timer->sequence())); assert(result.second); (void)result; } assert(timers_.size() == activeTimers_.size()); return earliestChanged; //如果新加入的timer到时时间最近,则earliestChanged为true,反则为false}

根据源码注释可以很好理解。我们继续回到上面的addTimerInLoop函数。接下来它会根据earliestChanged设置timefd_,这个不久赘述了。

  接下来看一下定时器到时后的处理流程:
在TimeQuene构造函数中:

timerfdChannel_.setReadCallback(      boost::bind(&TimerQueue::handleRead, this));  // we are always reading the timerfd, we disarm it with timerfd_settime.  timerfdChannel_.enableReading();

因此定时器到时时会调用handleRead函数,这是所有定时器处理器的公共入口。

void TimerQueue::handleRead(){  loop_->assertInLoopThread();  //得到当前的时间  Timestamp now(Timestamp::now());  readTimerfd(timerfd_, now);  //获取在这个时间点到时的所有定时器,并把他们存储在expired数组中  //同时这个函数也会把这些定时器从timers_中删除掉  std::vector
expired = getExpired(now); //开始处理定时器事件 callingExpiredTimers_ = true; cancelingTimers_.clear(); // safe to callback outside critical section for (std::vector
::iterator it = expired.begin(); it != expired.end(); ++it) { //一次调用每个定时器的run函数,run函数里面会调用相应定时器设置的回调函数。 it->second->run(); } callingExpiredTimers_ = false; //因为可能有些定时器需要在一定间隔内循环触发,这里会刷新一遍定时器队列 reset(expired, now);}

这个函数核心就是处理定时器事件。我们还记得在设置定时器的时候,有一个参数叫做internal,这个参数的意思是需要每个internal的时间都调用一次定时器处理函数。对于这样的定时器,我们不能调用一次后直接把它删掉,因此需要重新把它设置到timers_中,这就是最后一行语句reset(expired, now);的作用,当然它还负责重新设置timefd_:

void TimerQueue::reset(const std::vector
& expired, Timestamp now){ Timestamp nextExpire; for (std::vector
::const_iterator it = expired.begin(); it != expired.end(); ++it) { ActiveTimer timer(it->second, it->second->sequence()); //如果这个定时器是需要重复启动的,即internal>0 if (it->second->repeat() && cancelingTimers_.find(timer) == cancelingTimers_.end()) { it->second->restart(now);//则重启定时器,其实就是重新设置对应timer的到时时间:expiration_ insert(it->second); //把定时器重新加到Timequene中 } else { // FIXME move to a free list delete it->second; // 否则就将定时器删除掉,记住expired中的所有定时器已经移出timers_了 } } //找到最近到时的定时器,并重新设置timefd if (!timers_.empty()) { nextExpire = timers_.begin()->second->expiration(); } if (nextExpire.valid()) { resetTimerfd(timerfd_, nextExpire); }}

到这里定时器的内容就OK了,后面我们将学习muduo怎么处理IO事件中的读写,关键是缓冲区的设置。

你可能感兴趣的文章
一个简单的makefile示例及其注释
查看>>
python mysql
查看>>
高效人士的八个习惯
查看>>
mysql 赋给用户权限 grant all privileges on
查看>>
读取文件的几种方法
查看>>
yast 创建本地数据源
查看>>
vim 编码方式(encoding、fileencoding、fileencodings、termencoding介绍)
查看>>
程序员的十层楼
查看>>
windows 下php支持curl
查看>>
获取文件夹文件(C++)
查看>>
判断文件夹是否存在
查看>>
快速的内存分配器
查看>>
java中super 的两种用法
查看>>
bdb及其在php下扩展的安装
查看>>
bdb及其在php下扩展的安装
查看>>
android 小问题
查看>>
BerkeleyDB安装及配置
查看>>
标准的Activity Actions
查看>>
关于Android requires .class compatibility set to 5.0. Please fix project properties.的错误
查看>>
JAVA中implements实现多接口
查看>>