【SUBJECT技术】定时器实现原理及应用举例

一、定时器实现原理

1. timerfd 应用时序图

时钟管理器的应用包括典型的几个类,DeviceStatusService 是用来监听 timerFd 的,timerFd 是一个文件句柄,因此它包含了 epoll 的几步典型操作, 如 EpollCreate, AddEpoll,Wait EPOLLIN 和 EpollClose。当有时钟被触发时将在 wait EPOLL 中等到,然后执行始终回调函数 callback();
DragManager 是一个典型的应用方,它需要等间隔时间去做一些周期性的事情或者延迟一定时间之后去做一些事情,于是它在开始时添加一个时钟,里面有一个到时要做的事情的回调函数,成功后会产生一个时钟 timerId,结束时钟应用的时候需要移除该 timerId 对应的时钟;
TimerManager 是多个时钟管理器类,一个子系统当中有多个时钟定时器,就需要根据时钟的触发时间进行排队、添加、移除管理,这个里面维系了一个重要的数据结构 struct TimerItem,详细说明如后所述, 该类调用 Timerfd 提供的基础功能。
Timerfd 是 OpenHarmony 系统底层提供的一个时钟管理器,它提供了典型的两个函数(timerfd_gettime 没用到)和一个数据结构,如后所述。

2. 定时器序列管理

在传统的 IO 多路复用系统中,定时操作通常是直接去设置 poll()等函数的超时时间,系统超时之后去执行对应的定时回调。OpenHarmony 系统已经将时间也抽象为了一种事件,并提供了对应的文件描述符 fd 机制,其能够被 poll()等多路复用函数进行监视,因此可以很简洁地融入到我们之前实现的 Reactor 机制中。这也是更科学的做法。

定时设置,也就是用户指定一个时刻和一个回调函数,系统需要在该时刻去执行该回调函数。这个定义包含了两层含义:

用户的定时设置是任意的。有可能先设置一个 10 秒的定时,接下来再设置一个 5 秒的定时。用户不一定会以 expiration time 的先后顺序去设置定时;
系统顺序执行回调函数。系统需要将所有注册过的定时事件,按照 expiration time 的先后顺序,在用户设置的每一个 expiration time 去执行对应的回调函数;
基于上述的特点,我们需要的定时器模块的流程示意图所示。

首先解释一下图示的流程

class Timer: 显然每一个 expiration time 都对应着一个 callback function,因此将其封装为一个类 class Timer,方便执行各自的回调函数。在图中即对应着五个不同颜色的小圆圈;
接口:TimerQueue 向用户提供 Timer Insert 接口,供用户插入各个 Timer。在图中,我们先后插入了 expiration time 为 10s, 18s, 20s, 15s, 10.01s 的 Timer;
class TimerQueue 内部过程:① 将得到的所有 Timer 进行排序;② 每当有新的 Timer expired 之时,抽取出过期的 Timer,在图中对应着 10s 和 10.01s 两个 Timer;③ 依次执行这些 expired Timer 的回调函数;

接下来则是根据流程所示的特点去分析如何构建 class TimerQueue

首先,是何时执行 timerfd_create()。这个很简单,自然是在构造函数中执行,将得到的 timer fd 作为 class TimerQueue 的成员变量;

其次,是何时执行 timerfd_settime()。这个是需要重点思考的问题。最直接的解决方案自然是每插入一个 Timer 就执行一次 timerfd_settime(),对应图 1 的例子,系统就会将 10s, 10.01s, 15s, 18s, 20s 这几个时刻都设置为过期时刻,随后在每一个过期时刻将对应的 Timer 取出,并执行回调函数。但这个方案有一个潜在的问题。timerfd_settime()是系统调用,频繁的调用会导致系统开销增大。
改进的思路就是只对最早过期的 Timer 设置 timerfd_settime()。具体而言分两种情况:
新插入一个 Timer:在插入 Timer 之前比较该 Timer 和 TimerQueue 中排名最靠前的 Timer 的 expiration time,如果在其之前,则增设一个 timerfd_settime();
取出过期的 Timer:在取出了过期的 Timer 之后,如果 TimerQueue 中还有剩余的 Timer,则再对残余的 Timer 中最靠前的那一个 Timer 设置 timerfd_settime()

随后,是如何组织 TimerQueue 中的 Timer。我们必须保证有序,而插入新 Timer 的位置又是随机的。综合考虑,使用 std::set 是最方便的。问题是如果存在 expiration time 相同的 Timer 怎么办。muduo 源码给出的解决方法很巧妙,其将 std::set 的元素设置为 std::pair<TimeStamp, Timer *>,这样一来就完美的解决了问题,即使两个 Timer 的过期时刻一致,在 std::set 中也还能用 Timer 的地址去排列各个 Timer 的先后顺

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值