muduo网络库学习笔记(10):定时器的实现

本文介绍了为何在muduo网络库中选择使用timerfd作为定时器实现,并探讨了timerfd相比其他定时方法的优势。muduo的定时器由TimerId、Timer和TimerQueue三个类组成,重点分析了TimerQueue的内部机制,包括添加、取消定时器以及处理超时事件的过程。
摘要由CSDN通过智能技术生成

传统的Reactor通过控制select和poll的等待时间来实现定时,而现在在Linux中有了timerfd,我们可以用和处理IO事件相同的方式来处理定时,代码的一致性更好。

为什么选择timerfd

常见的定时函数有如下几种:

sleep 
alarm 
usleep 
nanosleep 
clock_nanosleep 
getitimer / setitimer 
timer_create / timer_settime / timer_gettime / timer_delete 
timerfd_create / timerfd_gettime / timerfd_settime

我们之所以选择timerfd,是因为:
1.sleep / alarm / usleep 在实现时有可能用了信号 SIGALRM,在多线程程序中处理信号是个相当麻烦的事情,应当尽量避免
2.nanosleep 和 clock_nanosleep 是线程安全的,但是在非阻塞网络编程中,绝对不能用让线程挂起的方式来等待一段时间,程序会失去响应。正确的做法是注册一个时间回调函数。
3.getitimer 和 timer_create 也是用信号来传递超时,在多线程程序中也会有麻烦。
4.timer_create 可以指定信号的接收方是进程还是线程,算是一个进步,不过在信号处理函数(signal handler)能做的事情实在很受限。
5.timerfd_create 把时间变成了一个文件描述符,该“文件”在定时器超时的那一刻变得可读,这样就能很方便地融入到 select/poll 框架中,用统一的方式来处理 IO 事件和超时事件,这也正是 Reactor 模式的长处。

timerfd相关函数介绍:

#include <sys/timerfd.h>

/**
 * 此函数用于创建一个定时器文件
 * 参数clockid可以是CLOCK_MONOTONIC或者CLOCK_REALTIME
 * 参数flags可以是0或者TFD_CLOEXEC/TFD_NONBLOCK
 * 函数返回值是一个文件句柄fd
 */
int timerfd_create(int clockid, int flags);

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

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

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的定时器功能由三个class实现,TimerId、Timer、TimerQueue,用户只能看到第一个class,另外两个都是内部实现细节。

TimerId被设计用来取消Timer的,它的结构很简单,只有一个Timer指针和其序列号。其中还声明了TimerQueue为其友元,可以操作其私有数据。

Timer是对定时器的高层次抽象,封装了定时器的一些参数,例如超时回调函数、超时时间、超时时间间隔、定时器是否重复、定时器的序列号。其函数大都是设置这些参数,run()用来调用回调函数,restart()用来重启定时器(如果设置为重复)。

重点介绍一下TimerQueue类。

TimerQueue class

TimerQueue的接口很简单,只有两个函数addTimer()和cancel()。它的内部有channel,和timerfd相关联。添加新的Timer后,在超时后,timerfd可读,会处理channel事件,之后调用Timer的回调函数;在timerfd的事件处理后,还会检查一遍超时定时器,如果其属性为重复还会再次添加到定时器集合中。

时序图:
这里写图片描述

(1)TimerQueue数据结构的选择
TimerQueue需要高效地组织目前尚未到期的Timer,能快速地根据当前时间找到已经到期的Timer,也要能高效地添加和删除Timer。因而可以用二叉搜索树(例如std::set/std::map),把Timer按到期时间先后排好序,其操作的复杂度是O(logN),但我们使用时还要处理两个Timer到期时间相同的情况(map不支持key相同的情况),做法如下:

// 两种类型的set,一种按时间戳排序,一种按Timer的地址排序
// 实际上,这两个set保存的是相同的定时器列表

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

typedef std::pair<Timer*, int64_t> ActiveTimer;
typedef std::
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值