木铎源码剖析—TimerQueue

TimerQueue

成员变量

// 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);
//在事件循环中取消指定TimerId对应的定时器
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_;
//定时器文件描述符
const int timerfd_;
//用于监听定时器文件描述符的通道
Channel timerfdChannel_;
// Timer list sorted by expiration
// 定时器列表 按照到期事件排序
TimerList timers_;
//活跃的定时器集合 用于处理定时器到期
// for cancel()
ActiveTimerSet activeTimers_;
//用于标识是否正在处理到期的定时器 atomic
bool callingExpiredTimers_; 
//用于存储正在取消的定时器集合
ActiveTimerSet cancelingTimers_;

全局方法:createTimerfd() 创建计时器文件描述符

//创建计时器文件描述符
int createTimerfd(){
  //创建并操作计时器 该计时器通过文件描述符传递计时器过期通知
  //CLOCK_MONOTONIC 表示使用系统的单调时钟 TFD_NONBLOCK:非阻塞 TFD_CLOEXEC:在执行exec调用时自动关闭文件描述符
  int timerfd = ::timerfd_create(CLOCK_MONOTONIC,
                                 TFD_NONBLOCK | TFD_CLOEXEC);
  if (timerfd < 0)
  {
    LOG_SYSFATAL << "Failed in timerfd_create";
  }
  return timerfd;
}

全局方法:howMuchTimeFromNow用于计算从当前事件到指定时间点的事件间隔,返回timespec结构体

struct timespec howMuchTimeFromNow(Timestamp when)
{
  int64_t microseconds = when.microSecondsSinceEpoch()
                         - Timestamp::now().microSecondsSinceEpoch();
  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;
}

全局方法:readTimerfd获取在文件描述符中存储的到期次数

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";
  }
}

全局方法:resetTimerfd

通过timerfd_settime函数重新设置定时器文件描述符

该函数的目的是通过重新设置定时器的到期时间,唤醒事件循环的执行

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);
  //这个函数会将定时器文件描述符的到期时间设置为newValue,通过oldvalue返回之前的到期时间
  int ret = ::timerfd_settime(timerfd, 0, &newValue, &oldValue);
  if (ret)
  {
    LOG_SYSERR << "timerfd_settime()";
  }
}

构造函数需要完成定时器文件描述符的创建,以及各类回调函数的绑定

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();
}

下面展示如何添加定时器,并放入事件循环器中,共涉及到三个函数如下所示;

首先构建timer结构体,实际会将执行函数放到循环器中,执行插入,并判断此定时器是否是最早改变的,如果是的话,则会重置定时器的到期时间;返回的是一个TimerId的结构体,存储了定时器,及序列号。

TimerId TimerQueue::addTimer(TimerCallback cb,
                             Timestamp when,
                             double interval)
{
  Timer* timer = new Timer(std::move(cb), when, interval);
  loop_->runInLoop(
      std::bind(&TimerQueue::addTimerInLoop, this, timer));
  return TimerId(timer, timer->sequence());
}

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

  if (earliestChanged)
  {
    resetTimerfd(timerfd_, timer->expiration());
  }
}

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)
  {
    earliestChanged = true;
  }
  {
    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;
}

取消某一个定时器

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

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;
    delete it->first; // FIXME: no delete please
    activeTimers_.erase(it);
  }
  else if (callingExpiredTimers_)
  {
    cancelingTimers_.insert(timer);
  }
  assert(timers_.size() == activeTimers_.size());
}

handleRead成员方法构建

void TimerQueue::handleRead()
{
  loop_->assertInLoopThread();
  Timestamp now(Timestamp::now());
  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;

  reset(expired, now);
}

std::vector<TimerQueue::Entry> TimerQueue::getExpired(Timestamp now)
{
  assert(timers_.size() == activeTimers_.size());
  std::vector<Entry> expired;
  Entry sentry(now, reinterpret_cast<Timer*>(UINTPTR_MAX));
  //寻找第一个大于或等于sentry的迭代器
  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())
    {
      it.second->restart(now);
      insert(it.second);
    }
    else
    {
      // FIXME move to a free list
      delete it.second; // FIXME: no delete please
    }
  }

  if (!timers_.empty())
  {
    nextExpire = timers_.begin()->second->expiration();
  }

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

总结:该定时器队列中用到了最关键的结构体set,底层是基于平衡二叉树实现的 ,可以自动排列插入的数据的大小,模型是从小到大的,在该类中维护了一个关于定时器的文件描述符,并且提供了添加定时器,移除定时器这两个最为关键的方法,为了可以实现长期循环,这两个函数的实际实现均放到了EventLoop中,对于添加定时器,除了要将定时器插入set外还需要判断该定时器是否是最早会触发的,如果是那么需要充值文件描述符的触发时间;删除定时器只需要判断该定时器是否在set中,来进行删除,值得注意的是,我们发现在定时器的存储上采用了两个对象进行存储,一个是以<Timestamp,Timer>进行存储,另一个则是通过<Timer,int64_t>来存储的,int64_t是定时器内部的一个序列号,他是由静态原子变量进行维护的,作用是每创建一个定时器该序列号会自定加1,这样每个定时器的序列号都是不一样的,创建两个对象存储数据,同样可以保证在执行数据操作时的一致性;而对于定时器文件描述符到期后,会触发回调函数handleRead,通过该函数可以获取到期的定时器列表,当然这些定时器都会被从set中清理出来,在重置后会添加进去。

posted @ 2024-02-28 20:36  孟秋十三  阅读(20)  评论(0)    收藏  举报