muduo网络库之TimerQueue类

这篇博客针对TimerQueue类做下小结。

博客代码来自于陈硕的muduo网络库,github地址https://github.com/chenshuo/muduo

工作原理:

TimerQueue顾名思义表示该类管理这一个时间队列,这个时间队列的内部结构是std::set,该set的元素类型是std::pair<Timestamp, Timer*>,即先按时间戳对定时队列从小到大进行排序,对于时间戳相同的定时再按地址值排序,该set保证了定时队列按时间排序保存。TimerQueue类管理着一个“真正的”定时器,这个“真正的”定时器在TimerQueue的构造期间就被注册到Poller中了,它把最先到达的定时设置为定时器的到期时间,其描述符为timerfd_,TimerQueue类有一个Channel对象timerfdChannel_,timerfd_和timerfdChannel_打包注册到Poller类中。当定时器定时时间到了,EventLoop的loop函数会通知timerfdChannel_的读回调,而timerfdChannel_的读回调就是TimerQueue的读回调,在TimerQueue的读回调中会遍历定时队列,把到期的定时通过getExpired找出来,执行各自的定时到期回调函数。执行完这些回调函数后把当前过期定时中的循环定时重新加到定时队列,并重新设置“真正的”定时器的下一次定时时间,这样一次完整的定时器回调就完成了。

学习笔记:

TimerQueue类的实现就是用一个“真正的”系统定时器管理了多个用户定时器,一个用户定时器就是一个Timer类对象,Timer类非常简单,封装了定时3要素(定时回调、定时时间、定时间隔)。TimerQueue类作为EventLoop的内部类,EventLoop对TimerQueue类的所有操作都是在同一个IO线程完成的,不存在多线程抢占的问题,只需要判断当前执行线程是否在EventLoop对象的创建线程即可。

使用方法:

用户一般不直接对TimerQueue进行操作,而是调用EventLoop类的是3个接口(runAt()、runAfter()、runEvery()),通过这3个接口把定时3要素(定时回调、定时时间、定时间隔)传进去,然后EventLoop类的TimerQueue对象把定时3要素封装成Timer插入定时队列timers_中,等待定Poller定时时间到来的通知。这里的TimerQueue类和Poller类同作为EventLoop类的成员类,用户可见的只有EventLoop类,可进行的定时操作也只有上面3个接口。

TimerQueue类的头文件

#ifndef MUDUO_NET_TIMERQUEUE_H
#define MUDUO_NET_TIMERQUEUE_H
 
#include <set>
#include <vector>
 
#include "muduo/base/Mutex.h"
#include "muduo/base/Timestamp.h"
#include "muduo/net/Callbacks.h"
#include "muduo/net/Channel.h"
 
namespace muduo
{
namespace net
{
 
class EventLoop;
class Timer;
class TimerId;
 
///
/// A best efforts timer queue.
/// No guarantee that the callback will be on time.
///
class TimerQueue : noncopyable
{
 public:
  explicit TimerQueue(EventLoop* loop);
  ~TimerQueue();
 
  ///
  /// Schedules the callback to be run at given time,
  /// repeats if @c interval > 0.0.
  ///
  /// Must be thread safe. Usually be called from other threads.
  TimerId addTimer(TimerCallback cb,
                   Timestamp when,
                   double interval);
 
  void cancel(TimerId timerId);
 
 private:
 
  // FIXME: use unique_ptr<Timer> instead of raw pointers.
  // This requires heterogeneous comparison lookup (N3465) from C++14
  // so that we can find an T* in a set<unique_ptr<T>>.
  typedef std::pair<Timestamp, Timer*> Entry;
  typedef std::set<Entry> TimerList;
  typedef std::pair<Timer*, int64_t> ActiveTimer;
  typedef std::set<ActiveTimer> ActiveTimerSet;
 
  void addTimerInLoop(Timer* timer);
  void cancelInLoop(TimerId timerId);
  // called when timerfd alarms
  void handleRead();
  // move out all expired timers
  std::vector<Entry> getExpired(Timestamp now);
  void reset(const std::vector<Entry>& expired, Timestamp now);
 
  bool insert(Timer* timer);
 
  EventLoop* loop_;       //TimerQueue所属的loop,对定时器的操作在loop这个IO线程完成,避免rece-condition
  const int timerfd_;     //定时器描述符
  Channel timerfdChannel_;//将被注册到Poller中,Poller调用::poll()阻塞式等待定时时间
  // Timer list sorted by expiration
  TimerList timers_;      //保存该loop下所有的定时
 
  // for cancel()
  ActiveTimerSet activeTimers_;   //感觉没啥用
  bool callingExpiredTimers_; /* atomic */
  ActiveTimerSet cancelingTimers_;//延时删除队列
};
 
}  // namespace net
}  // namespace muduo
#endif  // MUDUO_NET_TIMERQUEUE_H

TimerQueue类的cpp文件

#ifndef __STDC_LIMIT_MACROS
#define __STDC_LIMIT_MACROS
#endif

#include "muduo/net/TimerQueue.h"

#include "muduo/base/Logging.h"
#include "muduo/net/EventLoop.h"
#include "muduo/net/Timer.h"
#include "muduo/net/TimerId.h"

#include <sys/timerfd.h>
#include <unistd.h>

namespace muduo
{
namespace net
{
namespace detail
{

int createTimerfd()
{
  int timerfd = ::timerfd_create(CLOCK_MONOTONIC,
                                 TFD_NONBLOCK | TFD_CLOEXEC);
  if (timerfd < 0)
  {
    LOG_SYSFATAL << "Failed in timerfd_create";
  }
  return timerfd;
}

struct timespec howMuchTimeFromNow(Timestamp when)
{
  int64_t microseconds = when.microSecondsSinceEpoch()
                         - Timestamp::now().microSecondsSinceEpoch();
  //when时间-now时间可能为负
  if (microseconds < 100)
  {
    microseconds = 100;
  }
  struct timespec ts;
  ts.tv_sec = static_cast<time_t>(
      microseconds / Timestamp::kMicroSecondsPerSecond);
  ts.tv_nsec = static_cast<long>(
      (microseconds % Timestamp::kMicroSecondsPerSecond) * 1000);
  return ts;
}

void readTimerfd(int timerfd, Timestamp now)
{
  uint64_t howmany;
  ssize_t n = ::read(timerfd, &howmany, sizeof howmany);
  LOG_TRACE << "TimerQueue::handleRead() " << howmany << " at " << now.toString();
  if (n != sizeof howmany)
  {
    LOG_ERROR << "TimerQueue::handleRead() reads " << n << " bytes instead of 8";
  }
}

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);
  int ret = ::timerfd_settime(timerfd, 0, &newValue, &oldValue);
  if (ret)
  {
    LOG_SYSERR << "timerfd_settime()";
  }
}

}  // namespace detail
}  // namespace net
}  // namespace muduo

using namespace muduo;
using namespace muduo::net;
using namespace muduo::net::detail;

TimerQueue::TimerQueue(EventLoop* loop)
  : loop_(loop),
    timerfd_(createTimerfd()),
    timerfdChannel_(loop, timerfd_),
    timers_(),
    callingExpiredTimers_(false)
{
  timerfdChannel_.setReadCallback(
      std::bind(&TimerQueue::handleRead, this));
  // we are always reading the timerfd, we disarm it with timerfd_settime.
  timerfdChannel_.enableReading();
}

TimerQueue::~TimerQueue()
{
  //将注册到Poller中的Channel对象设置为空事件,并从Channels队列移除
  timerfdChannel_.disableAll();
  timerfdChannel_.remove();
  ::close(timerfd_);
  // do not remove channel, since we're in EventLoop::dtor();
  for (const Entry& timer : timers_)
  {
    //保证addTimer中new的Timer对象不会造成内存泄漏
    delete timer.second;
  }
}

TimerId TimerQueue::addTimer(TimerCallback cb,
                             Timestamp when,
                             double interval)
{
  //当该定时过期、cancel或TimerQueue对象析构时回收timer堆对象
  Timer* timer = new Timer(std::move(cb), when, interval);
  loop_->runInLoop(
      std::bind(&TimerQueue::addTimerInLoop, this, timer));
  //返回的TimerId用于定时队列对该定时进行cancel操作
  return TimerId(timer, timer->sequence());
}

void TimerQueue::cancel(TimerId timerId)
{
  loop_->runInLoop(
      std::bind(&TimerQueue::cancelInLoop, this, timerId));
}

void TimerQueue::addTimerInLoop(Timer* timer)
{
  loop_->assertInLoopThread();
  bool earliestChanged = insert(timer);

  if (earliestChanged)
  {
    //当插入的新定时比定时队列所有定时抢先到达,则重置定时器为改定时时间
    resetTimerfd(timerfd_, timer->expiration());
  }
}

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));
    assert(n == 1); (void)n;
    //保证addTimer中new的Timer对象不会造成内存泄漏
    delete it->first; // FIXME: no delete please
    activeTimers_.erase(it);
  }
  else if (callingExpiredTimers_)
  {
    //特殊情况:IO线程A正在调用该定时的回调,IO线程B同事cancel该定时,
    //则先把该定时放到待删除队列,当IO线程A执行完回调函数后再删除该定时
    cancelingTimers_.insert(timer);
  }
  assert(timers_.size() == activeTimers_.size());
}

void TimerQueue::handleRead()
{
  loop_->assertInLoopThread();
  Timestamp now(Timestamp::now());
  //读取完定时器描述符的可读数据,Poller为水平触发,不读完数据则会持续触发读事件
  readTimerfd(timerfd_, now);
  //获取截止到当前时间点所有过期定时
  std::vector<Entry> expired = getExpired(now);

  callingExpiredTimers_ = true;
  cancelingTimers_.clear();
  // safe to callback outside critical section
  for (const Entry& it : expired)
  {
    //执行过期定时的回调函数
    it.second->run();
  }
  callingExpiredTimers_ = false;
  //将过期定时中的循环定时重新插入到定时队列,
  //将非循环过期定时delete掉
  reset(expired, now);
}

std::vector<TimerQueue::Entry> TimerQueue::getExpired(Timestamp now)
{
  assert(timers_.size() == activeTimers_.size());
  std::vector<Entry> expired;
  //设置一个当前时间点的最大地址值,lower_bound函数将获取小于等于该时间点的所有定时即为到期定时
  Entry sentry(now, reinterpret_cast<Timer*>(UINTPTR_MAX));
  TimerList::iterator end = timers_.lower_bound(sentry);
  assert(end == timers_.end() || now < end->first);
  std::copy(timers_.begin(), end, back_inserter(expired));
  //定时队列暂时移除这些过期定时,如果这些定时中有可用的循环定时后续再重新加入进来
  timers_.erase(timers_.begin(), end);

  for (const Entry& it : expired)
  {
    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;
}

void TimerQueue::reset(const std::vector<Entry>& expired, Timestamp now)
{
  Timestamp nextExpire;

  for (const Entry& it : expired)
  {
    ActiveTimer timer(it.second, it.second->sequence());
    if (it.second->repeat()
        && cancelingTimers_.find(timer) == cancelingTimers_.end())
    {
      /*定时被重新放回定时队列需同时满足2个条件:
        1,该定时是循环定时;
        2,该定时在其它IO线程没有被cancel掉
      */
      it.second->restart(now);
      insert(it.second);
    }
    else
    {
      // FIXME move to a free list
      //保证addTimer中new的Timer对象不会造成内存泄漏
      delete it.second; // FIXME: no delete please
    }
  }

  if (!timers_.empty())
  {
    //每执行完一次超时回调需要重新设置定时器的下一次定时到期时间
    nextExpire = timers_.begin()->second->expiration();
  }

  if (nextExpire.valid())
  {
    resetTimerfd(timerfd_, nextExpire);
  }
}

bool TimerQueue::insert(Timer* timer)
{
  loop_->assertInLoopThread();
  assert(timers_.size() == activeTimers_.size());
  bool earliestChanged = false;
  Timestamp when = timer->expiration();
  TimerList::iterator it = timers_.begin();
  if (it == timers_.end() || when < it->first)
  {
    //当定时队列为空或者新插入的定时比当前队列所有定时抢先到达时需要更新当前注册到Poller的定时器时间
    earliestChanged = true;
  }
  {
    //新插入的定时会被timers_这个set根据定时时间和对象地址自行排好序
    std::pair<TimerList::iterator, bool> result
      = timers_.insert(Entry(when, timer));
    assert(result.second); (void)result;
  }
  {
    std::pair<ActiveTimerSet::iterator, bool> result
      = activeTimers_.insert(ActiveTimer(timer, timer->sequence()));
    assert(result.second); (void)result;
  }

  assert(timers_.size() == activeTimers_.size());
  return earliestChanged;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值