muduo net库学习笔记3——定时器的实现

在常见的定时函数中muduo选择timerfd实现定时器,原因如下:

  1. sleep / alarm / usleep在实现时有可能使用了SIGALRM信号,多线程程序中尽量避免使用信号,因为处理起来比较麻烦(信号通知进程,所有线程都将接收到这个信号,谁处理好)。另外,如果网络库定义了信号处理函数,用户代码(main函数等使用库的程序)也定义了信号处理函数,这不就冲突了,该调用哪个好
  2. nanosleep / clock_nanosleep是线程安全的,但是会让当前线程挂起等待一段时间,这会导致线程失去响应。在非阻塞网络编程中,绝对不能让线程挂起的方式来等待一段事件。正确的做法是注册一个事件回调函数
  3. gettimer和timer_create也是用信号来传递超时信息,在多线程中程序中也会有麻烦
  4. timerfd_create 把时间变成了一个文件描述符,该描述符在定时器超时的那一刻变为可读,可以很方便的融入到select/poll/epoll中,用同一的方式来处理IO事件和超时事件

timerfd相关函数介绍

# include<sys/timerfd.h>
int timerfd_create(int clockid, int flags);//创建一个定时器文件
		/*
		clockid可以是CLOCL_MONOTONIC或者CLOCK_REALTIME
		参数flags可以是0或者TFD_CLOEXEC/TFD_NONBLOCK
		函数返回值是一个文件句柄fd
		*/


int timerfd_settime(int fd, int flags, const struct itimerspec *new_value, struct itimerspec *old_value);//设置新的超时时间,并开始计时
		/**
		 * 参数fd是timerfd_create返回的文件句柄
		 * 参数flags为TFD_TIMER_ABSTIME(1)代表设置的是绝对时间;为0代表相对时间
		 * 参数new_value为需要设置的超时和间隔时间
		 * 参数old_value为定时器这次设置之前的超时时间
		 * 函数返回0代表设置成功
		 */

int timerfd_gettime(int fd, struct itimerspec *curr_value);//获得定时器距离下次超时还剩下的时间
		/**
		 * 此函数用于获得定时器距离下次超时还剩下的时间
		 * 如果调用时定时器已经到期,并且该定时器处于循环模式
		 * 即设置超时时间时struct itimerspec::it_interval不为0
		 * 那么调用此函数之后定时器重新开始计时
		 */ 
		 
itimerspec结构体;
struct itimerspec {
    struct timespec it_interval;  // interval for periodic timer
    struct timespec it_value;     // initial expiration
};

struct timespec {
    time_t tv_sec;  // seconds
    long tv_nsec;   // nano-seconds
};

muduo定时器涉及的类

muduo的定时器功能由三个类实现,TimerIdTimerTimerQueue
Timer类是一个超时任务,保存超时时间,回调函数,以及记录自己是否是周期性计时任务,回调函数是用户提供的
TimerId类用于保存超时任务Timer和它独一无二的id
TimerQueue类保存用户设置的所有超时任务,需要高效保存尚未超时的任务,同时需要有序,方便找到超时时间最近的那个任务,可以用最小堆(libevent采用),也可以用std::set或者set::map存储(muduo采用)

定时任务的原理

  1. muduo采用timerfd_*将超时任务转换成文件描述符进行监听
  2. 当时间一到,timerfd变为可读,相应的Channel调用回调函数(TimerQueue::handleRead)
  3. 回调函数中将所有在TimerQueue中的超时任务找出,一次调用其回调函数
  4. 对于周期性定时任务,再添加回TimerQueue
    在这里插入图片描述整个过程只有一个timerfdPoller监听,所以调用timerfd_settime设置的超时时间一定是TimerQueueset/map里最小的,即set.begin();第一个Timer任务。
    而用户是通过调用EventLoop::runAt/runAfter/runEvery这些EventLoop的函数注册定时任务的,这些函数都需要向TimerQueueset或者map中添加Timer,所以每添加一个都需要判断新添加的定时任务的超时时间是否小于设置的超时时间,如果小于,就需要调用timerfd_settime重新设置timerfd的超时时间。
    而每次timerfd被激活都需要找到在set中所有的超时任务,因为有可能存在超时时间相等的定时任务,可以使用std::lower_bound函数找到第一个大于等于给定值的位置

重点看看TimerQueue

TimerQueue由所在的EventLoop持有,用户设置定时任务也是调用的EventLoop的接口,进而调用TimerQueue的接口

EventLoop提供三个接口用于注册定时任务

实际调用的是TimerQueue的addTimer接口

/* 
 * 定时器功能,由用户调用runAt并提供当事件到了执行的回调函数
 * 时间在Timestamp设置,绝对时间,单位是微秒
 */
TimerId EventLoop::runAt(Timestamp time, TimerCallback cb)
{
  /* std::move,移动语义,避免拷贝 */
  return timerQueue_->addTimer(std::move(cb), time, 0.0);
}

/*
 * 如上,单位是微秒,相对时间
 */
TimerId EventLoop::runAfter(double delay, TimerCallback cb)
{
  Timestamp time(addTime(Timestamp::now(), delay));
  return runAt(time, std::move(cb));
}

/*
 * 每隔多少微秒调用一次
 */
TimerId EventLoop::runEvery(double interval, TimerCallback cb)
{
  Timestamp time(addTime(Timestamp::now(), interval));
  return timerQueue_->addTimer(std::move(cb), time, interval);
}

TimerQueue.h

TimerQueue是保存着timerfd和所有的定时任务,回调函数,以及添加/删除定时任务的类

#ifndef MUDUO_NET_TIMERQUEUE_H
#define MUDUO_NET_TIMERQUEUE_H

#include <set>
#include <vector>

#include <boost/noncopyable.hpp>

#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;//前向声明,避免#include,只是定义了并没有用那仅仅声明就ok
class Timer;
class TimerId;

class TimerQueue : boost::noncopyable
{
 public:
  TimerQueue(EventLoop* loop);
  ~TimerQueue();

  // 一定是线程安全的,可以跨线程调用。通常情况下被其它线程调用。
  TimerId addTimer(const TimerCallback& cb,
                   Timestamp when,
                   double interval);//注册定时任务

  void cancel(TimerId timerId);//取消定时任务,每个定时任务都有对应的TimerId, 这是addTimer返回给调用者的  `TimerId`类用于保存超时任务Timer和它独一无二的id

 private:

  // FIXME: use unique_ptr<Timer> instead of raw pointers.
  // unique_ptr是C++ 11标准的一个独享所有权的智能指针
  // 无法得到指向同一对象的两个unique_ptr指针
  // 但可以进行移动构造与移动赋值操作,即所有权可以移动到另一个对象(而非拷贝构造)
  typedef std::pair<Timestamp, Timer*> Entry;
  typedef std::set<Entry> TimerList;
  
  typedef std::pair<Timer*, int64_t> ActiveTimer;
  typedef std::set<ActiveTimer> ActiveTimerSet;

  // 以下成员函数只可能在其所属的I/O线程中调用,因而不必加锁。
  // 服务器性能杀手之一是锁竞争,所以要尽可能少用锁
  void addTimerInLoop(Timer* timer);
  void cancelInLoop(TimerId timerId);

  // called when timerfd alarms 当timerfd被激活时调用的回调函数,标识超时
  void handleRead();//回调函数

  // 返回超时的定时器列表
  std::vector<Entry> getExpired(Timestamp now);//从timers_中拿出所有超时的Timers
  void reset(const std::vector<Entry>& expired, Timestamp now);//将超时任务中周期性的任务重新添加到times_中

  bool insert(Timer* timer);//插入到timers_中
  
  EventLoop* loop_;  // 所属的EventLoop
  const int timerfd_;//由timerfd_create创建的文件描述符 timerfd
  Channel timerfdChannel_;//用于监听timerfd_的Channel
  TimerList timers_;  // timers_是按到期时间排序

  // for cancel()
  // timers_与activeTimers_保存的是相同的数据
  // timers_是按到期时间排序,activeTimers_是按对象地址排序
  ActiveTimerSet activeTimers_;
  bool callingExpiredTimers_;  // 是否正在处理超时事件
  ActiveTimerSet cancelingTimers_;  // 保存的是被取消的定时器
};

}
}
#endif  //MUDUO_NET_TIMERQUEUE_H

TimerQueue.cc

addTimer

主要添加函数和回调函数,添加函数是addTimer,由用户调用EventLoop的注册函数runAt/runAfter/runEveny后,再由注册函数调用添加函数, 并在set中添加事件

TimerId TimerQueue::addTimer(const TimerCallback& cb,/*用户提供的回调函数,时间到就执行*/
                             Timestamp when,/*超时时间*/
                             double interval/*是否调用runEvery,即是否是周期的*/)
{
  Timer* timer = new Timer(cb, when, interval);
  addTimerInLoop(timer);
  return TimerId(timer, timer->sequence());
}

所以实际是调用的addTimerInLoop()

void TimerQueue::addTimerInLoop(Timer* timer)//在计时队列中添加时间
{
  loop_->assertInLoopThread();
  // 插入一个定时器,有可能会使得最早到期的定时器发生改变
  bool earliestChanged = insert(timer);//返回true,说明timer被添加到set的顶部,作为新的根节点,需要更新timerfd的激活时间, insert函数👇

  if (earliestChanged)
  {
    // 重置定时器的超时时刻(timerfd_settime)
    resetTimerfd(timerfd_, timer->expiration());
  }
}

插入函数主要任务是将某个定时任务插入到定时任务set中,同时判断新添加的这个定时任务的超时时间和之前设置的超时时间的大小(位于set的根节点处),如果新添加的定时任务超时时间小,就需要更新timerfd的超时时间(返回true),然后调用resetTimerfd使用timerfd_settime重新设置超时时间

// 插入一个timer
bool TimerQueue::insert(Timer* timer)
{
  loop_->assertInLoopThread();
  assert(timers_.size() == activeTimers_.size());//断言保证
  bool earliestChanged = false;//默认值
  Timestamp when = timer->expiration();//新加入的超时时间
  TimerList::iterator it = timers_.begin();//第一个元素,是超时时间最近的Timer、

  // 如果要添加的超时时间比最短时间短,那就要更新Queue中的时间
  if (it == timers_.end() || when < it->first)
  {
    earliestChanged = true;
  }
  {
    // 插入到set中
    std::pair<TimerList::iterator, bool> result
      = timers_.insert(Entry(when, timer));
    assert(result.second); (void)result;
  }
  {
    // 插入到activeTimers_中,用于删除时查找
    std::pair<ActiveTimerSet::iterator, bool> result
      = activeTimers_.insert(ActiveTimer(timer, timer->sequence()));
    assert(result.second); (void)result;
  }

  assert(timers_.size() == activeTimers_.size());
  return earliestChanged;
}
回调函数handleRead()

timerfd被激活,表明定时任务超时,进而调用回调函数,即TimerQueue::handleRead这个回调函数是在构造函数中构造Channel时候注册的
回调函数先调用getExpired从定时任务set中取出所有超时任务,然后执行其回调函数,最后判断取出的这些超时任务有没有周期性的,如果有,就将周期性任务添加回set中

void TimerQueue::handleRead()
{
  loop_->assertInLoopThread();
  Timestamp now(Timestamp::now());
  readTimerfd(timerfd_, now);  // 读timerfd

  // 获取该时刻之前所有的定时器列表(即超时定时器列表)  从定时任务set中拿出所有超时任务
  std::vector<Entry> expired = getExpired(now);//getExpired👇

  callingExpiredTimers_ = true;
  cancelingTimers_.clear();

  for (std::vector<Entry>::iterator it = expired.begin();//调用超时的事件回调函数
      it != expired.end(); ++it)
  {
    // 这里回调定时器处理函数
    it->second->run();
  }
  callingExpiredTimers_ = false;

  // 把重复的定时器重新加入到定时器中
  reset(expired, now);
}

getExpired主要就是将set中的超时任务拿出,因为set是有序的,直接调用std::lower_bound找到第一个大于等于当前时间的定时任务,前面的所有任务都是超时的,全部取出

std::vector<TimerQueue::Entry> TimerQueue::getExpired(Timestamp now)
{
  assert(timers_.size() == activeTimers_.size());
  std::vector<Entry> expired;
  // UINTPTR_MAX表示最大的地址
  Entry sentry(now, reinterpret_cast<Timer*>(UINTPTR_MAX));

  // 返回第一个未到期的Timer的迭代器
  // lower_bound的含义是返回第一个值>=sentry的元素的iterator
  // 即*end >= sentry,从而end->first > now
  // 注意:此处是>,而不是>=
  TimerList::iterator end = timers_.lower_bound(sentry);
  assert(end == timers_.end() || now < end->first);

  // 将[begin end)区间的元素(到期的)追加到expired末尾
  std::copy(timers_.begin(), end, back_inserter(expired));

  // 从timers_中移除到期的定时器
  timers_.erase(timers_.begin(), end);

  // 从activeTimers_中移除到期的定时器
  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); (void)n;
  }

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

调用完回调函数之后需要将周期性任务重新添加到set中,不过记得要重新计算超时时间

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

    // 如果是重复的定时器并且不在cancelingTimers_集合中,则重启该定时器
    if (it->second->repeat()
        && cancelingTimers_.find(timer) == cancelingTimers_.end())
    {
      it->second->restart(now);//重新计算超时事件
      insert(it->second);//重新弄添加到set中
    }
    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())
  {
    // 重置定时器的超时时刻(timerfd_settime)
    resetTimerfd(timerfd_, nextExpire);
  }
}
完整.cc代码
#define __STDC_LIMIT_MACROS
#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 <boost/bind.hpp>

#include <sys/timerfd.h>

namespace muduo
{
namespace net
{
namespace detail
{

// 创建定时器,用到了timerfd_create()
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();

  // 精确度没有设置那么高,所以小于100ms时都置为100
  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;
}

// 处理超时事件。超时后,timerfd变为可读
void readTimerfd(int timerfd, Timestamp now)
{
  uint64_t howmany;  // 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";
  }
}

// 重置定时器的超时时间,用到了timerfd_settime()
void resetTimerfd(int timerfd, Timestamp expiration)
{
  // wake up loop by timerfd_settime()
  struct itimerspec newValue;
  struct itimerspec oldValue;
  bzero(&newValue, sizeof newValue);
  bzero(&oldValue, sizeof oldValue);
  newValue.it_value = howMuchTimeFromNow(expiration);
  int ret = ::timerfd_settime(timerfd, 0, &newValue, &oldValue);
  if (ret)
  {
    LOG_SYSERR << "timerfd_settime()";
  }
}

}
}
}

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

// 构造函数
TimerQueue::TimerQueue(EventLoop* loop)
  : loop_(loop),
    timerfd_(createTimerfd()),  // 创建timerfd
    timerfdChannel_(loop, timerfd_),  // timerfd关联的channel
    timers_(),
    callingExpiredTimers_(false)
{
  timerfdChannel_.setReadCallback(
      boost::bind(&TimerQueue::handleRead, this));
  // we are always reading the timerfd, we disarm it with timerfd_settime.
  timerfdChannel_.enableReading();  // timerfd对应的channel监听事件为可读事件
}

// 析构函数
TimerQueue::~TimerQueue()
{
  ::close(timerfd_);
  // do not remove channel, since we're in EventLoop::dtor();
  for (TimerList::iterator it = timers_.begin();
      it != timers_.end(); ++it)
  {
    delete it->second;  // 手动释放Timer*
  }
}

// 添加新的定时器
TimerId TimerQueue::addTimer(const TimerCallback& cb,
                             Timestamp when,
                             double interval)
{
  Timer* timer = new Timer(cb, when, interval);
  addTimerInLoop(timer);
  return TimerId(timer, timer->sequence());
}

// 取消定时器
void TimerQueue::cancel(TimerId timerId)
{
  cancelInLoop(timerId);
}

// 添加定时器时实际调用了addTimerInLoop()
void TimerQueue::addTimerInLoop(Timer* timer)
{
  loop_->assertInLoopThread();
  // 插入一个定时器,有可能会使得最早到期的定时器发生改变
  bool earliestChanged = insert(timer);

  if (earliestChanged)
  {
    // 重置定时器的超时时刻(timerfd_settime)
    resetTimerfd(timerfd_, timer->expiration());
  }
}

// 取消定时器时实际调用了cancelInLoop()
void TimerQueue::cancelInLoop(TimerId timerId)
{
  loop_->assertInLoopThread();
  assert(timers_.size() == activeTimers_.size());
  ActiveTimer timer(timerId.timer_, timerId.sequence_);  // 要取消的定时器timer
  // 查找该定时器
  ActiveTimerSet::iterator it = activeTimers_.find(timer);

  // 要取消的在当前激活的Timer集合中
  if (it != activeTimers_.end())
  {
    size_t n = timers_.erase(Entry(it->first->expiration(), it->first));  // 从timers_中移除
    assert(n == 1); (void)n;
    delete it->first;  // FIXME:如果用了unique_ptr,这里就不需要手动删除了
    activeTimers_.erase(it);  // 从activeTimers_中移除
  }
  // 如果正在执行超时定时器的回调函数,则加入到cancelingTimers集合中
  else if (callingExpiredTimers_)
  {
    cancelingTimers_.insert(timer); 
  }
  assert(timers_.size() == activeTimers_.size());
}

void TimerQueue::handleRead()
{
  loop_->assertInLoopThread();
  Timestamp now(Timestamp::now());
  readTimerfd(timerfd_, now);  // 读timerfd

  // 获取该时刻之前所有的定时器列表(即超时定时器列表)
  std::vector<Entry> expired = getExpired(now);

  callingExpiredTimers_ = true;
  cancelingTimers_.clear();

  for (std::vector<Entry>::iterator it = expired.begin();
      it != expired.end(); ++it)
  {
    // 这里回调定时器处理函数
    it->second->run();
  }
  callingExpiredTimers_ = false;

  // 把重复的定时器重新加入到定时器中
  reset(expired, now);
}

// rvo即Return Value Optimization
// 是一种编译器优化技术,可以把通过函数返回创建的临时对象给”去掉”
// 然后达到少调用拷贝构造的操作,从而提高性能
std::vector<TimerQueue::Entry> TimerQueue::getExpired(Timestamp now)
{
  assert(timers_.size() == activeTimers_.size());
  std::vector<Entry> expired;
  // UINTPTR_MAX表示最大的地址
  Entry sentry(now, reinterpret_cast<Timer*>(UINTPTR_MAX));

  // 返回第一个未到期的Timer的迭代器
  // lower_bound的含义是返回第一个值>=sentry的元素的iterator
  // 即*end >= sentry,从而end->first > now
  // 注意:此处是>,而不是>=
  TimerList::iterator end = timers_.lower_bound(sentry);
  assert(end == timers_.end() || now < end->first);

  // 将[begin end)区间的元素(到期的)追加到expired末尾
  std::copy(timers_.begin(), end, back_inserter(expired));

  // 从timers_中移除到期的定时器
  timers_.erase(timers_.begin(), end);

  // 从activeTimers_中移除到期的定时器
  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); (void)n;
  }

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

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

    // 如果是重复的定时器并且不在cancelingTimers_集合中,则重启该定时器
    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())
  {
    // 重置定时器的超时时刻(timerfd_settime)
    resetTimerfd(timerfd_, nextExpire);
  }
}

// 插入一个timer
bool TimerQueue::insert(Timer* timer)
{
  loop_->assertInLoopThread();
  assert(timers_.size() == activeTimers_.size())bool earliestChanged = false;
  Timestamp when = timer->expiration();
  TimerList::iterator it = timers_.begin();

  // 如果timers_为空或者when小于timers_中的最早到期时间
  if (it == timers_.end() || when < it->first)
  {
    earliestChanged = true;
  }
  {
    // 插入到timers_中
    std::pair<TimerList::iterator, bool> result
      = timers_.insert(Entry(when, timer));
    assert(result.second); (void)result;
  }
  {
    // 插入到activeTimers_中
    std::pair<ActiveTimerSet::iterator, bool> result
      = activeTimers_.insert(ActiveTimer(timer, timer->sequence()));
    assert(result.second); (void)result;
  }

  assert(timers_.size() == activeTimers_.size());
  return earliestChanged;
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值