muduo 31 timer定时器模块

本文详细解析了Muduo库中的定时器系统,包括Timer类、TimerQueue的设计与实现逻辑,以及如何使用timerfd和EventLoop进行定时任务管理。重点介绍了TimerQueue的高效组织方式和事件分发机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

muduo中定时器模块的特点

muduo中的定时器系统

muduo中定时器实现的逻辑:

Timer类

TimerQueue类

使用timerfd实现定时功能

timer_create

timerfd_settime

创建TimerQueue

删除定时器管理对象​编辑

插入定时器流程

处理到期定时器


muduo中定时器模块的特点

  1. 整个TimerQueue只使用一个timerfd来观察定时事件,并且每次重置timerfd时只需跟set中第一个节点比较即可,因为set本身是一个有序队列。
  2. 整个定时器队列采用了muduo典型的事件分发机制,可以使的定时器的到期时间像fd一样在Loop线程中处理。
  3. 之前Timestamp用于比较大小的重载方法在这里得到了很好的应用。

muduo中的定时器系统

          在muduo的定时器系统中,一共由四个类:Timestamp,Timer,TimeId,TimerQueue组成。其中最关键的是Timer和TimerQueue两个类。该项目中没有使用Timeld类。

        TimerQueue类,是整个定时器设施的核心,其他三个类简介其作用。 其中Timestamp是一个以int64_t表示的微秒级绝对时间,而Timer则表示一个定时器的到时事件,是否具有重复唤醒的时间等,TimerId表示在在TimerQueue中对Timer的索引

muduo中定时器实现的逻辑:

①:Timer类:Timer类包含了一个超时时间戳和一个回调函数。当超时时间戳到达时,调用回调函数出发定时事件。

②:TimerQueue类:TimerQueue类是一个基于事件戳排序的定时器容器。它使用了最小堆(MinHeap)数据结构来保证定时器按照超时时间的顺序进行排列。TimerQueue类提供了添加、删除和获取最近超时的定时器的接口。

③:EventLoop类:EventLoop类是muduo网络库的核心组件,负责事件的循环和处理。其中包括定时器事件的管理。EventLoop有一个成员变量TimerQueue timerQueue_,用于存储定时器对象。EventLoop会在事件循环中监测定时器队列中最近超时的定时器,并调用其回调函数。

Timer类

  1. 定时器到期后需要回调函数;
  2. 定时器需要记录我们的超时时间;
  3. 如果是重复事件(比如每间隔5秒扫描一次用户连接),我们还需要记录超时间间隔;
  4. 对应的,我们需要一个bool类型的值标注这个定时器是一次性的还是重复的。

对于不是一次性的定时器,我们通过restart方法,观察定时器的构造函数中

repeat_(interval > 0.0) // 一次性定时器设置为0

如果是需要重新利用的定时器,会调用restart方法,我们设置其下一次超时时间为「当前时间 + 间隔时间」。如果是「一次性定时器」,那么就会自动生成一个空的 Timestamp,其时间自动设置为 0.0

TimerQueue类

TimerQueue类管理作为管理定时器的结构。其内部使用 STL 容器 set 来管理定时器。我们以时间戳作为键值来获取定时器。set 内部实现是红黑树,红黑树中序遍历便可以得到按照键值排序过后的定时器节点。小顶堆,说明最小的事件戳(即最早出发的定时器位于容器顶部)

using Entry = std::pair<Timestamp, Timer*>;
using TimerList = std::set<Entry>;

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

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

使用timerfd实现定时功能

timerfd与IO多路复用机制(如epoll)结合使用,可以实现基于事件得事件驱动编程。当定时器到期时,可以通过epoll等机制监视timerfd得可读事件,并触发相应得事件处理逻辑。当超时事件发生时,该文件描述符就变为可读。

timer_create
timerfd_create(CLOCK_MONOTONIC,TFD_NONBLOCK | TFD_CLOEXEC);

timerfd_settime
int timerfd_settime(int fd,int flags
                    const struct itimerspec *new_value
                    struct itimerspec *old_value);
                    //成功返回0

 我们使用此函数启动或停止定时器。

创建TimerQueue

  1. 通过timer_create创建timerfd
  2. TimerQueue类初始化后,设置其timerfdChannel绑定读事件,并置于可读状态

删除定时器管理对象

插入定时器流程

1、EventLoop调用方法,加入一个定时器事件,传入定时器回调函数,超时时间和间隔时间(为0.0则为一次性定时器),addTimer方法根据这些属性构造新得定时器。

2、定时器队列内部插入此定时器,并判断这个定时器得超时时间是否比先前得都早。如果是最早触发的,就会调用resetTimerfd方法重新设置tiemrfd_的触发时间。内部会根据超时时间和现在时间计算出新的超时时间。

 内部实现的插入方法获取此定时器的超时时间,如果比先前的时间小就说明第一个触发。那么我们会设置好布尔变量。因此这涉及到timerfd_的触发时间。

重置timerfd_  :通过计算时间差使用timerfd_settime将新的到期时间设置到指定定时器事件得文件描述符上

处理到期定时器

  1. EventLoop获取活跃的activeChannel,并分别调取它们绑定的回调函数。这里对于timerfd_,就是调用了handleRead方法
  2. handleRead方法获取已经超时的定时器组数组,遍历到期的定时器并调用内部绑定的回调函数。之后调用reset方法重新设置定时器
  3. reset方法内部判断这些定时器是否是可重复使用的,如果是则继续插入定时器管理队列,之后自然会触发事件。如果不是,那么就删除。如果重新插入了定时器,记得还需重置timerfd_。

①:ReadTimerFd读取定时器文件描述符(timerfd)的值:

②: getExpired获取已经到期的定时器列表:

  • getExpired函数接收一个时间戳参数now,表示当前时间。
  • 首先,创建一个空的expired向量,用于存储已经到期的定时器节点。
  • 创建一个临时的定时器节点sentry,其时间戳为now,指针为一个特殊值UINTPTR_MAX,用于作为一个哨兵节点。(这里设置所有定时器都不会超时,可以自己定义)
  • 使用lower_bound函数在timers_容器中查找第一个大于或等于sentry的节点,返回一个迭代器指向该位置,存储在end中。
  • 使用std::copy函数将timers_容器中从起始位置到end位置的节点复制到expired向量中。
  • 使用erase函数从timers_容器中删除从起始位置到end位置的节点。(set小根堆,越小说明发生越早,所以删除前半段)
  • 最后,返回存储了已删除的到期定时器节点的expired向量。

③:遍历需要删除的定时器,执行回调函数:

④:重新设置定时器之前,调用reset函数对已到期的定时器进行处理:

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值