浅析muduo库中的定时器设施

2023-11-14

一个设计良好的定时器在服务端的应用程序上至关重要,muduo定时器的实现陈硕大牛在书中已经详细的谈过,笔者尝试从源码的角度解读定时器的实现,如果理解不对,欢迎指正。

在muduo的定时器系统中,一共由四个类:Timestamp,Timer,TimeId,TimerQueue组成。其中最关键的是Timer和TimerQueue两个类。此文只解释初读时让人非常迷惑的TimerQueue类,这个类是整个定时器设施的核心,其他三个类简介其作用。

其中Timestamp是一个以int64_t表示的微秒级绝对时间,而Timer则表示一个定时器的到时事件,是否具有重复唤醒的时间等,TimerId表示在在TimerQueue中对Timer的索引。

TimerQueue

下面是muduo定时器中最重要的TimerQueue类,是整个定时器的核心,初读时让人非常迷惑,最主要的原因还是没有搞清楚Timer类中的成员的意思。


/**Timer.h**/

 private:

  const TimerCallback callback_;//定时器回调函数

  Timestamp expiration_;//绝对的时间

  const double interval_;//如果有重复属性,超时的时间间隔

  const bool repeat_;//是否有重复

  const int64_t sequence_;//定时器序号



  static AtomicInt64 s_numCreated_;//定时器计数

有了上述成员的意义,我们便可以介绍TimerQueue的功能了。


/**TimerQueue.h**/

class TimerQueue : boost::noncopyable

{

 public:

  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(const TimerCallback& cb,

                   Timestamp when,

                   double interval);//往定时器队列中添加定时器

#ifdef __GXX_EXPERIMENTAL_CXX0X__

  TimerId addTimer(TimerCallback&& cb,

                   Timestamp when,

                   double interval);

#endif



  void cancel(TimerId timerId);//取消某个定时器



 private:



  // FIXME: use unique_ptr<Timer> instead of raw pointers.

  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_;

  const int timerfd_;//只有一个定时器,防止同时开启多个定时器,占用多余的文件描述符

  Channel timerfdChannel_;//定时器关心的channel对象

  // Timer list sorted by expiration

  TimerList timers_;//定时器集合(有序)



  // for cancel()

  // activeTimerSet和timer_保存的是相同的数据

  // timers_是按照到期的时间排序的,activeTimerSet_是按照对象地址排序

  ActiveTimerSet activeTimers_;//保存正在活动的定时器(无序)

  bool callingExpiredTimers_; /* atomic *///是否正在处理超时事件

  ActiveTimerSet cancelingTimers_;//保存的是取消的定时器(无序)

};

上述代码中有三处让人感到惊喜的地方:

  • 首先,整个TimerQueue之打开一个timefd,用以观察定时器队列队首的到期事件。其原因是因为set容器是一个有序队列,以<排序,就是说整个队列中,Timer的到期时间时从小到大排列的,正是因为这样,才能做到节省系统资源的目的。

  • 其次,在整个TimerQueue类中有三个容器,一个表示有序的Timer队列,一个表示正在活动的,无序的定时器队列(用于与有序的定时器队列同步),还有一个表示取消的定时器队列(在重新启动一个有固定时间间隔定时器时,首先判断是否友重复属性,其次就是是否在已经取消的队列中)。第二个定时器队列是否多余?还没有想明白。

  • 最后,整个定时器队列采用了muduo典型的事件分发机制,可以使的定时器的到期时间像fd一样在Loop线程中处理。


/**TimerQueue.cc**/

int createTimerfd()

{//创建非阻塞timefd

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

  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)

{//处理超时时间,超时后,timefd变为可读,howmany表示超时的次数

  uint64_t howmany;//将事件读出来,免得陷入Loop忙碌状态

  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;

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

    timerfdChannel_(loop, timerfd_),

    timers_(),

    callingExpiredTimers_(false)

{

  timerfdChannel_.setReadCallback(

      boost::bind(&TimerQueue::handleRead, this));

  // we are always reading the timerfd, we disarm it with timerfd_settime.

  timerfdChannel_.enableReading();//设置Channel的常规步骤

}



TimerQueue::~TimerQueue()

{

  timerfdChannel_.disableAll();//channel不再关注任何事件

  timerfdChannel_.remove();//在三角循环中删除此Channel

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

  loop_->runInLoop(

      boost::bind(&TimerQueue::addTimerInLoop, this, timer));

  return TimerId(timer, timer->sequence());

}



#ifdef __GXX_EXPERIMENTAL_CXX0X__

TimerId TimerQueue::addTimer(TimerCallback&& cb,

                             Timestamp when,

                             double interval)

{

  Timer* timer = new Timer(std::move(cb), when, interval);

  loop_->runInLoop(

      boost::bind(&TimerQueue::addTimerInLoop, this, timer));

  return TimerId(timer, timer->sequence());

}

#endif



void TimerQueue::cancel(TimerId timerId)

{//取消定时器

  loop_->runInLoop(

      boost::bind(&TimerQueue::cancelInLoop, this, timerId));

}



void TimerQueue::addTimerInLoop(Timer* timer)

{

  loop_->assertInLoopThread();

  bool earliestChanged = insert(timer);//是否将timer插入set的首部



  //如果插入首部,更新timrfd关注的到期时间

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

  {//删除Timers_和activeTimers_中的Timer

    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);//删除活动的timer

  }

  else if (callingExpiredTimers_)

  {//将删除的timer加入到取消的timer队列中

    cancelingTimers_.insert(timer);//取消的定时器与重新启动定时器有冲突

  }

  assert(timers_.size() == activeTimers_.size());

}



void TimerQueue::handleRead()

{

  loop_->assertInLoopThread();

  Timestamp now(Timestamp::now());

  readTimerfd(timerfd_, now);//读timerFd,防止一直出现可读事件,造成loop忙碌



  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)//逐个调用超时的定时器的回调

  {

    it->second->run();

  }

  callingExpiredTimers_ = false;//退出处理超时定时器额状态



  reset(expired, now);//把具有重复属性的定时器重新加入定时器队列中

}



std::vector<TimerQueue::Entry> TimerQueue::getExpired(Timestamp now)

{//获得当前已经超时的timer

  assert(timers_.size() == activeTimers_.size());

  std::vector<Entry> expired;//存储超时timer的队列

  Entry sentry(now, reinterpret_cast<Timer*>(UINTPTR_MAX));

  TimerList::iterator end = timers_.lower_bound(sentry);//返回的一个大于等于now的timer,小于now的都已经超时

  assert(end == timers_.end() || now < end->first);

  std::copy(timers_.begin(), end, back_inserter(expired));//将timer_的begin到上述获得end迭代器元素添加到expired的末尾

  timers_.erase(timers_.begin(), end);//在timer_中删除刚才被添加的元素



  for (std::vector<Entry>::iterator it = expired.begin();

      it != expired.end(); ++it)

  {//在Activetimer_的同步中删除timer

    ActiveTimer timer(it->second, it->second->sequence());

    size_t n = activeTimers_.erase(timer);

    assert(n == 1); (void)n;

  }



  assert(timers_.size() == activeTimers_.size());//再次将timer_和activetimer同步

  return expired;//返回超时的timerQueue

}



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

    if (it->second->repeat()

        && cancelingTimers_.find(timer) == cancelingTimers_.end())

    {//判断是否具有重复属性并且不在取消的定时器队列中

      it->second->restart(now);//重新设置定时器的到期时间,并且将重新设置后的定时器插入timer_和activeTimer_中

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

  {//如果到期时间不为0,重新设置timerfd应该关注的时间

    resetTimerfd(timerfd_, nextExpire);

  }

}



bool TimerQueue::insert(Timer* timer)

{//将Timer插入到两个同步的TimeQueue中,最关键的一个函数

  loop_->assertInLoopThread();

  assert(timers_.size() == activeTimers_.size());//判断两个Timer队列的同步bool earliestChanged = false;

  Timestamp when = timer->expiration();//获得Timer的事件

  TimerList::iterator it = timers_.begin();//得到Timer的begin

  if (it == timers_.end() || when < it->first)

  {//判断是否要将这个timer插入队首,如果是,更新timefd关注的到期事件

    earliestChanged = true;

  }



  {//将Timer中按顺序插入timer_,set是有序集合,默认关键字<排列

    std::pair<TimerList::iterator, bool> result

      = timers_.insert(Entry(when, timer));

    assert(result.second); (void)result;

  }



  {//随意插入进入activeTimer_

    std::pair<ActiveTimerSet::iterator, bool> result

      = activeTimers_.insert(ActiveTimer(timer, timer->sequence()));

    assert(result.second); (void)result;

  }



  assert(timers_.size() == activeTimers_.size());//再次同步两个Timer

  return earliestChanged;

}

上述代码注释足够多,还是那个问题,无序的set是否有出现的必要?

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

浅析muduo库中的定时器设施 的相关文章

  • idea Maven异常:Could not find artifact(本地仓库确实存在)

    首先检查自己的maven环境有无问题 1 首先尝试清楚idea中的缓存如下图所示 当pom没有爆红时 按照上图操作还是无效 可以尝试重新新建一下自己的repository把自己原来的仓库重命名备份下以防万一 我的是这样操作解决了问题 希望能

随机推荐

  • 实战攻防演习之红队

    0x00 什么是红队 红队 一般是指网络实战攻防演习中的攻击一方 红队一般会针对目标系统 人员 软件 硬件和设备同时执行的多角度 混合 对抗性的模拟攻击 通过实现系统提权 控制业务 获取数据等目标 来发现系统 技术 人员和基础架构中存在的网
  • 【JavaEE】多线程案例-单例模式

    文章目录 1 前言 2 什么是单例模式 3 如何实现单例模式 3 1 饿汉模式 3 2 懒汉模式 4 解决单例模式中遇到的线程安全问题 4 1 加锁 4 2 加上一个判断解决频繁加锁问题 4 2 解决因指令重排序造成的线程不安全问题 1 前
  • 应用场域的深度融合与创新构想

    近年来 随着人工智能技术的迅速发展 自然语言处理技术也取得了显著的进步 其中 ChatGPT作为一种高效的自然语言处理模型 已经在许多领域得到了广泛的应用 本文主要围绕ChatGPT的调研分析以及其与应用场域的结合构想展开讨论 一 Chat
  • BigDecimal转String字符串的代码

    BigDecimal转String字符串的代码 Posted on 2016 06 28 11 17 上善其若水 厚德载物 阅读 8896 评论 0 编辑 收藏 举报 bigdeciaml stripTrailingZeros toPlai
  • 求最大子序列和的四种方法

    求一个给定序列的连续子序列中和最大的那个子序列的和 下边方法只求和 没有找出最大子序列 用到的头文件和宏定义如下 include stdafx h include
  • 机器学习:聚类算法实现流程

    学习目标 掌握K means聚类的实现步骤 k means其实包含两层内容 K 初始中心点个数 计划聚类数 means 求中心点到其他数据点距离的平均值 1 k means聚类步骤 1 随机设置K个特征空间内的点作为初始的聚类中心 2 对于
  • 抖音矩阵系统功能开发及开发文档说明

    抖音账号矩阵系统开发文档是矩阵系统开发的重要文件 涵盖了矩阵系统的设计 实现 测试和维护等方面的相关信息 这些信息是矩阵系统开发人员进行系统开发的重要参考 有助于确保系统开发的顺利进行和最终的实现效果 矩阵系统开发文档一般包括以下几个方面的
  • mysql的悲观锁和乐观锁

    悲观锁 悲观锁指的是对数据被外界 包括本系统当前的其他事务 以及来自外部系统的事务处理 修改持保守态度 因此 在整个数据处理过程中 将数据处于锁定状态 悲观锁的实现 往往依靠数据库提供的锁机制 也只有数据库层提供的锁机制才能真正保证数据访问
  • MyBatis框架的作用?

    1 MyBatis 是一个优秀的基于 java 的持久层框架 它内部封装了 jdbc 使开发者只需要关注 sql 语句本身 而不需要花费精力去处理加载驱动 创建连接 创建 statement 等繁杂的过程 2 MyBatis为了和数据库进行
  • C++函数(详细版)

    函数 函数指针 内联函数 decltype关键字 atuo关键字 返回引用 const关键字 二维数组 函数指针 概念 函数也是有地址的 而指向这个地址的指针就是函数指针 1 获取函数的地址 使用函数名即可 例如think 是一个函数 则t
  • Python编程挑战赛

    题1 给小朋友分糖 每人分到糖的数量不同 输入小朋友的数量 计算至少需要多少糖 思路 第1个小朋友1颗糖 第2个小朋友2颗糖 第3个小朋友3颗糖 第n个小朋友n颗糖 计算1 2 n的和即可 第1种写法 不用Python高级函数 n int
  • 安装和简单使用visual studio 2017

    1 安装visual studio installer小程序 VS 2017社区版 Community 下载地址 百度网盘下载链接 百度网盘 请输入提取码 密码 ub6c 2 在visual studio installer里下载安装vis
  • Vue3 自定义指令

    在前端项目中 有很多需求是需要在多页面进行逻辑处理 通常我们所需要的功能可以通过DOM操作来实现 或者多处功能一致 我们就可以使用自定义指令 在vue2中的自定义指令指令注册的方法是 v focus js import vue from v
  • 概率论之 -- 边缘分布

    边缘分布 Marginal Distribution 指在概率论和统计学的多维随机变量中 只包含其中部分变量的概率分布 中文名 边缘分布 外文名 marginal distribution 又 名 边际分布 应用学科 概率论 统计学 定义
  • 还在用夸克?这3款能安装插件的手机浏览器不香吗

    说到浏览器插件 很多人想到的多数是电脑上的玩法 实际上 随着手机浏览器功能越来越完善 很多手机浏览器已经开始支持插件的使用 也就是说 支持安装插件的手机浏览器 不仅能体验如电脑般丝滑强大的功能 而且又不会造成内存过分臃肿 开启响应缓慢的问题
  • GLSL着色器的正确文件扩展名是什么?

    openGL系列文章目录 文章目录 openGL系列文章目录 前言 一 glslangValidator exe工具使用 二 着色器程序后缀名 前言 我正在学习glsl着色 我遇到了不同的文件格式 我见过人们给出他们的顶点和片段着色器 ve
  • Java中在特定区间产生随机数

    原文地址 http blog sina com cn s blog 59aebaa10100ct47 html 参考地址 http blog csdn net codefunjava article details 44408555 htt
  • 贝叶斯分类器-机器学习ML

    参考 1 统计学习方法 李航 2 https baike baidu com item E8 B4 9D E5 8F B6 E6 96 AF E5 88 86 E7 B1 BB E5 99 A8 1739590 fr aladdin 3 h
  • 力扣|错误的集合 C语言

    题目连接 错误的集合 集合 s 包含从 1 到 n 的整数 不幸的是 因为数据错误 导致集合里面某一个数字复制了成了集合里面的另外一个数字的值 导致集合 丢失了一个数字 并且 有一个数字重复 给定一个数组 nums 代表了集合 S 发生错误
  • 浅析muduo库中的定时器设施

    一个设计良好的定时器在服务端的应用程序上至关重要 muduo定时器的实现陈硕大牛在书中已经详细的谈过 笔者尝试从源码的角度解读定时器的实现 如果理解不对 欢迎指正 在muduo的定时器系统中 一共由四个类 Timestamp Timer T