项目模块七:TimerWheel模块
一、模块设计思想
在之前介绍了 Linux 定时器,详见具备技术一:Linux定时器-CSDN博客,所以这个模块的实现就是把 Linux 的定时器和时间轮任务队列整合在一起,来实现设置一个定时器,操控时间轮任务何时执行过期事件,来达到最终目的:对于超时连接的释放。
timerfd:实现每隔一段时间给进程一个超时事件。
timerwheel:每次执行 RuntimeTask 都可执行一波到期定时任务,要实现一个完整秒级定时器就要整合两个模块。
二、子模块一:TimerTask
详见具备技术一:Linux定时器-CSDN博客,由于只是一个任务,所以一点没有改变,真正要改变的是子模块二。
三、子模块二:TimerWheel
1、设计思路
要与 EventLoop 模块结合就一定要绑定一个 eventloop,由于需要和 timerfd 结合,所以还要有 timerfd,为了能够读取定时器里面的数据,所以要 poller 来管理文件描述符 timerfd,并且绑定读写回调函数用于读出事件就绪次数,便于处理事件。
2、成员变量
int timerfd:定时器文件描述符,用于读取写入就绪的事件并通知。
EventLoop *_loop:绑定一个 eventloop,用于线程安全处理事件。
unique_ptr<Channel> _timer_channel:用 Channel 管理 timerfd 就绪事件。
int _tick:指针指向哪里哪里的任务就超时要执行。
int _capacity:最大延迟时间
vector<vector<PtrTask>> _wheel:二维数组
unordered_map<uint64_t, WeakTask> _timers:辅助shared_ptr
3、成员函数
(1)私有函数1:删除定时任务 void RemoveTimer(int id)
找到哈希表里面的定时任务并删除。
auto pos = _timers.find(id);
_timers.erase(pos);
(2)私有函数2:创建定时器 int CreateTimerfd()
步骤:先创建 timerfd,然后再设置 timerfd 的超时时间
返回值:返回定时器文件描述符。原理:每隔一个超时时间系统会给描述符写入一个8字节数据代表已经超时几次。
clockid:
CLOCK_REALTIME:以系统时间作为基准值(若改变系统时间就会不准确)
CLOCK_MONOTONIC:系统启动时间进行递增的基准值(准确)
flags:0代表阻塞操作,TFD_NONBLOCK非阻塞操作。
fd:timerfd_create 返回值
flags:0表示相对时间
struct itimerspec:
new value:这次设置的超时时间。
old value:用于保存上次的超时时间
最后返回创建好的 timerfd
(3)私有函数3:读取定时器里面的超时次数 int ReadTimerfd()
用 read
注意:读写都是用8字节的数据!
(4)私有函数4:执行一次超时任务 void RunTask()
就是把存放任务的 vector 数组里面的任务清空,若任务真的被销毁了就会调用析构函数里面的超时连接释放函数。
(5)私有函数5:timerfd读回调函数 void OnTime()
先从 ReadTimerfd() 里面获取要执行几次超时任务,再 for 循环执行 RunTask()
(6)私有函数6:线程安全添加超时事件 void TImerAddInLoop(uint64_t id, uint32_t delay,const Task &task)
就是正常的添加事件,因为是私有接口,等到公有接口定义函数的时候才考虑线程安全。
(7)私有函数7:线程安全刷新定时任务 void TimerRefreshInLoop(uint64_t id)
同上
(8)私有函数8:线程安全删除定时任务 void TimerCancledLoop(uint64_t id)
同上
(9)公有函数1:构造函数
首先需要绑定一个 eventloop,然后一定要创建 timerfd,用 Channel 管理 timerfd,然后设置私有函数里面的 tiemrfd 读回调函数,最后开启 timerfd 读事件关心。一个步骤不能少!!!!!
(10)公有函数2,3,4:线程安全添加 / 刷新 / 删除定时任务
由于要用到 EventLoop 里面的 RunInLoop 函数来线程安全的执行添加 / 刷新 / 删除定时任务,所以不能在 TimerWheel 类的公有函数里面定义,而要在全局里面定义
(11)公有函数5:确定是否有指定任务 bool HasTask(uint64_t id)
在哈希表里面找一下有没有指定任务即可。