当前位置: 首页 > news >正文

timerfd定时器时间轮定时器

目录

一、timerfd定时器

二、timerfd定时器代码演示

三、时间轮定时器


一、timerfd定时器

timerfd是一种通过文件描述符管理定时器的机制

#include <sys/timerfd.h>

int timerfd_create(int clockid, int flags);

作用:创建定时器的文件描述符

返回值:创建成功返回定时器的文件描述符,失败返回-1并设置错误码

int clockid:指定定时器使用的时间。CLOCK_REALTIME使用系统实时时间,修改系统时间会影响定时器;CLOCK_MONOTONIC使用单调时间,即从系统启动的时间开始计算

int flags:设置文件描述符模式。TFD_NONBLOCK是非阻塞模式,读取后立即返回;TFD_CLOEXEC,执行exec进行进程替换时,关闭该定时器文件描述符


int timerfd_settime(int fd, int flags, const struct itimerspec *new_value, struct itimerspec *old_value);

作用:设置定时器的触发时间(每当定时器触发时,都会向定时器文件描述符中写入距离上一次读取已经触发的次数,如果一直没有读取定时器文件描述符中的数据,次数会一直累加)

返回值:成功返回0,失败返回-1并设置错误码

int fd:定时器文件描述符

int flags:指定定时器的计时方式。0表示使用相对时间,即相对于当前时间的偏移;TFD_TIMER_ABSTIME表示使用绝对时间,即一个具体的时间点

const struct itimerspec *new_value:新的定时器时间参数

struct itimerspec *old_value:旧的定时器时间参数(保存一下,需要可以恢复旧参数)

struct itimerspec结构体:

struct itimerspec {struct timespec it_interval; // 第一次超时后,每隔xxx时间读取数据struct timespec it_value;    // 第一次等待的时间
};
struct timespec {time_t tv_sec;  // 秒long   tv_nsec; // 纳秒 [0, 999999999]
};

二、timerfd定时器代码演示

定时器文件描述符为阻塞模式,定时器触发后立即读取,共循环10次

#include <iostream>
#include <sys/timerfd.h>
#include <unistd.h>//提供read函数void test()
{//创建timerfd定时器int timerfd=timerfd_create(CLOCK_REALTIME,0);if(timerfd<0){perror("Timerfd create fail!\n");return;}//设置定时器的触发时间struct itimerspec its={{3,0},{5,0}};//首次5秒触发,后续间隔3秒触发int n=timerfd_settime(timerfd,0,&its,nullptr);if(n<0){perror("Timerfd settime fail!\n");return;}//读取定时器文件描述符的数据uint64_t timeout;int count=10;while(count-->0){ssize_t m=read(timerfd,&timeout,sizeof(timeout));if(m>0){std::cout<<"距离上一次读取数据,已经触发"<<timeout<<"次"<<std::endl;}else{if(errno==EAGAIN){continue;}else{perror("Read fail\n");break;}}}//关闭定时器文件描述符close(timerfd);
}int main()
{test();return 0;
}

三、时间轮定时器

时间轮定时器思想:

时间轮的思想来源于钟表,如果我们定了⼀个3点钟的闹铃,则当时针⾛到3的时候,就代表时间到了。
同样的道理,如果我们定义了⼀个数组,并且有⼀个指针,指向数组起始位置,这个指针每秒钟向后⾛动⼀步,⾛到哪⾥,则代表哪⾥的任务该被执⾏了,那么如果我们想要定⼀个3s后的任务,则只需要将任务添加到tick+3位置,则每秒中⾛⼀步,三秒钟后tick⾛到对应位置,这时候执⾏对应位置的任务即可。
但是,同⼀时间可能会有⼤批量的定时任务,因此我们可以给数组对应位置下拉⼀个数组,这样就可以在同⼀个时刻上添加多个定时任务了。
当然,上述操作也有⼀些缺陷,⽐如我们如果要定义⼀个60s后的任务,则需要将数组的元素个数设置为60才可以,如果设置⼀⼩时后的定时任务,则需要定义3600个元素的数组,这样⽆疑是⽐较⿇烦的。
因此,可以采⽤多层级的时间轮,有秒针轮,分针轮,时针轮, 60<time<3600则time/60就是分针轮对应存储的位置,当tick/3600等于对应位置的时候,将其位置的任务向分针,秒针轮进⾏移动。 
因为当前我们的应⽤中,倒是不⽤设计的这么⿇烦,因为我们的定时任务通常设置的30s以内,所以简单的单层时间轮就够⽤了。但是,我们也得考虑⼀个问题,当前的设计是时间到了,则主动去执⾏定时任务,释放连接,那能不能在时间到了后,⾃动执⾏定时任务呢,这时候我们就想到⼀个操作-类的析构函数。 
⼀个类的析构函数,在对象被释放时会⾃动被执⾏,那么我们如果将⼀个定时任务作为⼀个类的析构函数内的操作,则这个定时任务在对象被释放的时候就会执⾏。但是仅仅为了这个⽬的,⽽设计⼀个额外的任务类,好像有些不划算,但是,这⾥我们⼜要考虑另⼀个问题,那就是假如有⼀个连接建⽴成功了,我们给这个连接设置了⼀个30s后的定时销毁任务,但是在第10s的时候,这个连接进⾏了⼀次通信,那么我们应该时在第30s的时候关闭,还是第40s的时候关闭呢?⽆疑应该是第40s的时候。也就是说,这时候,我们需要让这个第30s的任务失效,但是我们该如何实现这个操作呢?
这⾥,我们就⽤到了智能指针shared_ptr,shared_ptr有个计数器,当计数为0的时候,才会真正释放⼀个对象,那么如果连接在第10s进⾏了⼀次通信,则我们继续向定时任务中,添加⼀个30s后(也就是第40s)的任务类对象的shared_ptr,则这时候两个任务shared_ptr计数为2,则第30s的定时任务被释放的时候,计数-1,变为1,并不为0,则并不会执⾏实际的析构函数,那么就相当于这个第30s的任务失效了,只有在第40s的时候,这个任务才会被真正释放。

//时间轮定时器模块#include <iostream>
#include <sys/timerfd.h>
#include <functional>
#include <vector>
#include <unordered_map>
#include <memory>
#include <unistd.h>using TaskFunc=std::function<void()>;
using ReleaseFunc=std::function<void()>;//定时任务:时间到了定时任务就要被触发,执行
class TimerTask
{
private:TaskFunc _task;//定时任务,析构时执行uint64_t _id;//任务序号uint32_t _timeout;//定时任务触发时间(超时时间)ReleaseFunc _release;//当定时任务被触发时,删除TimerWheel时间轮中的定时任务对象,析构时执行bool _isCancel;//取消定时任务,false表示没有被取消,true表示被取消
public://构造函数TimerTask(uint64_t id,uint32_t timeout,const TaskFunc& task):_id(id),_timeout(timeout),_task(task),_isCancel(false){}//析构函数~TimerTask(){if(_isCancel==false) _task();//在析构函数中执行定时任务_release();//在析构函数中删除时间轮中的定时任务对象}//设置删除定时任务的函数void SetRelease(ReleaseFunc release){_release=release;}//获取定时任务的时间uint32_t GetTimeout(){return _timeout;}//取消定时任务(只能考虑在定时任务本身取消,而不是在时间轮中释放定时任务,因为释放定时任务等于提前执行定时任务,而不是取消)void Cancel(){_isCancel=true;}
};using SharedTask=std::shared_ptr<TimerTask>;
using WeakTask=std::weak_ptr<TimerTask>; //-------------------------------------------------------------------------------------------------------------------////时间轮:存储定时任务,并使用秒针来确定定时任务何时被执行
class TimerWheel
{
private:int _tick;//秒针int _capacity;//时间轮容量,本质是时间轮的最大延迟时间std::vector<std::vector<SharedTask>> _wheels;//二维数组存放定时任务,并且定时任务使用shared_ptr智能指针封装std::unordered_map<uint64_t,WeakTask> _timers;//建立定时任务序号和定时任务weak_ptr的映射关系,所有的定时任务都使用weak_ptr管理//因为当需要延迟定时任务时,增添相同的定时任务要使用shared_ptr针对shared_tr拷贝才会共享引用计数//如果直接对原始对象构造shared_ptr不会共享引用计数,因此使用weak_ptr管理原始对象,//所有的shared_ptr都是针对weak_ptr的拷贝,共享引用计数,但是weak_ptr本身没有引用计数private://删除时间轮中的定时任务void RemoveTimer(uint64_t id){auto it=_timers.find(id);if(it!=_timers.end()){_timers.erase(id);}}
public://构造函数TimerWheel():_tick(0),_capacity(60),_wheels(_capacity){}//析构函数~TimerWheel(){}//添加定时任务void AddTimerTask(uint64_t id,uint32_t timeout,const TaskFunc& task){//构建定时任务TimerTask对象SharedTask st(new TimerTask(id,timeout,task));st->SetRelease(std::bind(&TimerWheel::RemoveTimer,this,id));//在哈希表中建立定时任务序号和定时任务weak_ptr对象的映射关系_timers[id]=WeakTask(st);//在时间轮中添加定时任务_wheels[(_tick+st->GetTimeout())%_capacity].emplace_back(st);}//延迟/刷新定时任务:通过WeakTask构建出新的SharedTask智能指针,再将新的智能指针添加到时间轮中void RefreshTimerTask(uint64_t id){//通过WeakTask的lock函数构建出新的SharedTask智能指针auto it=_timers.find(id);if(it==_timers.end()){return;}SharedTask st=it->second.lock();//将新的智能指针添加到时间轮中_wheels[(_tick+st->GetTimeout())%_capacity].emplace_back(st);}//执行定时任务:时间轮的秒针指向哪里,哪里的定时任务就要被触发执行(本质是释放此处的SharedTask,引用计数-1)void RunTimerTask(){//int n=_wheels[_tick].size();// for(int i=0;i<n;i++)// {//     _wheels[_tick].pop_back();// }//秒针+1_tick=(_tick+1)%_capacity;//执行任务,即销毁shared_ptr对象(具体是否销毁取决于引用计数)_wheels[_tick].clear();}//取消定时任务void CancelTimerTask(uint64_t id){auto it=_timers.find(id);if(it==_timers.end()){return;}SharedTask st=it->second.lock();//通过weak_ptr构建出shared_ptrif(st!=nullptr) st->Cancel();//取消定时任务}
};//测试时间轮定时器
void Task()
{std::cout<<"任务处理中......"<<std::endl;std::cout<<"任务处理完毕!"<<std::endl;std::cout<<"--------------------"<<std::endl;
}
int main()
{TimerWheel tw;tw.AddTimerTask(1,3,std::bind(Task));tw.AddTimerTask(2,5,std::bind(Task));tw.AddTimerTask(3,9,std::bind(Task));tw.AddTimerTask(4,20,std::bind(Task));tw.AddTimerTask(4,20,std::bind(Task));//模拟30秒的时间for(int i=0;i<30;i++){std::cout<<i+1<<"秒:"<<std::endl;sleep(1);tw.RunTimerTask();//时间轮的秒针向后走1秒}return 0;
}

相关文章:

  • 机器学习:【抛掷硬币的贝叶斯后验概率】
  • 使用OpenAMP多核框架RPMsg实现高效控制和通信设计
  • 二极管钳位电路——Multisim电路仿真
  • 《Windows系统Java环境安装指南:从JDK17下载到环境变量配置》
  • leetcode 143. 重排链表
  • 解答UnityShader学习过程中的一些疑惑(持续更新中)
  • 在 Spring Boot 中实现异常处理的全面指南
  • Callable Future 实现多线程按照顺序上传文件
  • 知识付费平台推荐及对比介绍
  • 更新日期自动填充
  • ESG跨境电商怎么样?esg跨境电商有哪些功用?
  • 【动手学大模型开发】使用 LLM API:ChatGPT
  • 使用Curl进行本地MinIO的操作
  • 30天通过软考高项-第六天
  • MTKAndroid12-13-开机应用自启功能实现
  • Vue 对话框出现时,为什么滚动鼠标还是能滚动底层元素
  • Spring系列四:AOP切面编程第三部分
  • 软件工程(一):黑盒测试与白盒测试
  • 如何在WordPress网站中设置双重验证,提升安全性
  • 打火机检测数据集VOC+YOLO格式925张1类别
  • 法治日报调查直播间“杀熟”乱象:熟客越买越贵,举证难维权不易
  • 牛市早报|国家发改委:将推出做好稳就业稳经济推动高质量发展若干举措
  • 伊朗港口爆炸死亡人数升至70人
  • 最近这75年,谁建造了上海?
  • 当AI开始深度思考,人类如何守住自己的慢思考能力?
  • 坚守刑事检察一线13年,“在我心中每次庭审都是一次大考”