muduo中定时器的管理

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

boost::scoped_ptr<TimerQueue> timerQueue_;

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

TimerId runAt(const Timestamp& time, const TimerCallback& cb); //在时间点time调用cb
TimerId runAfter(double delay, const TimerCallback& cb);//过delay的时间后调用cb
TimerId 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时间再调用cb
TimerId 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<TimerList::iterator, bool> result
      = timers_.insert(Entry(when, timer));
    assert(result.second); (void)result;
  }
  {
   //将timer加入到ActiveTimersetstd::pair<ActiveTimerSet::iterator, bool> 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<Entry> expired = getExpired(now);

  //开始处理定时器事件
  callingExpiredTimers_ = true;
  cancelingTimers_.clear();
  // safe to callback outside critical section
  for (std::vector<Entry>::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<Entry>& expired, Timestamp now)
{
  Timestamp nextExpire;

  for (std::vector<Entry>::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事件中的读写,关键是缓冲区的设置。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值