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

C++项目:仿muduo库高并发服务器-------时间轮定时器


文章目录

  • 前言
  • 一、创建定时器
    • 1.1 timerfd_create 函数
    • 1.2 timerfd_settime
  • 二、管理定时任务
  • 三、代码实现


前言

本篇文章介绍的实现TimerQueue模块需要用到的语法知识、结构设计、以及具体代码实现,学习时请结合具体项目学习。

回顾:
TimerQueue模块是实现固定时间定时任务的模块,可以理解就是要给定时任务管理器,向定时任务管理器中添加⼀个任务,任务将在固定时间后被执行,同时也可以通过刷新定时任务来延迟任务的执行。

  • 功能:定时任务模块,让任务能在指定时间后执行
  • 意义:组件内部用于释放非活跃连接(希望非活跃连接在N秒后被释放 )
  • 功能设计:
    1. 添加定时任务
    2. 刷新定时任务(使定时任务重新开始计时 )
    3. 取消定时任务

这个模块主要是对Connection对象的⽣命周期管理,对非活跃连接进行超时后的释放功能。


目前我们先实现一个功能模块,后续会结合其他模块,根据需要进行调整

一、创建定时器

因项目需要,这里只简单学习,帮助我们理解时间轮实现

1.1 timerfd_create 函数

Linux 系统下的系统调用函数 timerfd_create,用于创建一个定时器对象,返回一个文件描述符。 定时器到期时,系统自动向其文件描述符写入 8 字节数据(uint64_t 类型表示 “距离上一次读取到现在超时次数”)。具体操作方式和文件操作一样。如:

定时周期: 定时器设为 3 秒超时(后面介绍如何设置 ),系统会每隔 3 秒向定时器文件描述符 “写入计数”。
计数含义: read 时拿到的 8 字节数据(uint64_t 类型),是从上次读之后到当前的 “超时次数”。比如 30 秒没读,超时次数 = 30/3 = 10 次,read 就会拿到 10 。

 #include <sys/timerfd.h>int timerfd_create(int clockid, int flags);

参数

  • clockid:指定定时器使用的时钟类型,常见取值如:
    1. CLOCK_REALTIME:系统实时时钟(受系统时间修改影响 )。
    2. CLOCK_MONOTONIC:单调递增时钟(不受系统时间调整影响,适合测量间隔 )。
  • flags:标志位,可组合使用(如 TFD_NONBLOCK 设为非阻塞),传 0 表示默认阻塞行为。

阻塞
timerfd_create 创建的文件描述符,默认是阻塞模式
比如用 read(tfd, ...) 读定时器到期事件时:

  • 若定时器没到期,read卡住(阻塞),线程/进程暂停执行,直到定时器到期、有数据可读才返回。

这种阻塞方式是比较适合这里的需要的,所以使用默认设置即可,时钟类型选择 CLOCK_MONOTONIC

1.2 timerfd_settime

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

用于配置 timerfd_create 创建的定时器,设置首次超时时间、周期超时间隔。

参数

  • fdtimerfd_create 返回的定时器文件描述符。
  • flags:控制行为(如 0 用相对时间)。

相对时间:是以当前时间为起始点来计算定时器的超时时间,如:在 12 点 0 分 0 秒调用 timerfd_settime ,那么定时器会已该时间作为基准判断是否超时。

  • newitimerspec 结构体指针,配置 首次超时(it_value周期间隔(it_interval,精度到纳秒(tv_sec 秒 + tv_nsec 纳秒 )。

首次超时指启动定时器后,经过设定时长即判定为超时;周期间隔指首次超时发生后,每隔设定时长就判定一次超时 。 比如 12 点 0 分 0 秒调用函数,若首次超时设为 3 秒、周期间隔设为 2 秒:定时器会在 12 点 0 分 3 秒触发第一次超时,此后每隔 2 秒(12 点 0 分 5 秒、12 点 0 分 7 秒…… )触发一次超时 。

struct timespec {time_t tv_sec;    /* 秒数 */long   tv_nsec;   /* 纳秒数 */
};struct itimerspec {struct timespec it_interval;  /* 周期性定时器的时间间隔 */struct timespec it_value;     /* 初始超时时间 */
};
  • old:若非 NULL,传出之前的定时器配置。
#include <iostream>
#include <cstdio>
#include <string>
#include <ctime>
#include <cstdlib>
#include <unistd.h>
#include <sys/timerfd.h>
#include <sys/select.h>int main()
{// 创建一个定时器int timerfd = timerfd_create(CLOCK_MONOTONIC, 0);struct itimerspec itm;itm.it_value.tv_sec = 3;        // 设置第一次超时的时间(秒)itm.it_value.tv_nsec = 0;       // 第一次超时的纳秒部分itm.it_interval.tv_sec = 3;     // 第一次超时后,每隔多长时间超时(秒)itm.it_interval.tv_nsec = 0;    // 周期间隔的纳秒部分// 启动定时器timerfd_settime(timerfd, 0, &itm, NULL);// 这个定时器描述符将每隔三秒都会触发一次可读事件time_t start = time(NULL);while (1) {uint64_t tmp;// 注意:定时器超时后,描述符触发可读事件,必须读取8字节数据// 数据保存的是自上次启动定时器或read后的超时次数int ret = read(timerfd, &tmp, sizeof(tmp));if (ret < 0) {return -1;}std::cout << tmp << " " << time(NULL) - start << std::endl;}close(timerfd);return 0;
}

二、管理定时任务

结合代码理解
在 TimerQueue 模块中,必然存在大量的超时任务。目前我们仅知道如何为任务创建定时器的方法,但关键问题在于:如何高效判断任务是否超时?若逐个读取定时器来检查,会产生较大的开销,显然采用定时器的方法是不合理的。因此,我们采用时间轮机制,以此实现对任务的高效超时管理。
在这里插入图片描述
如:一个任务被设置为三秒后执行,我们让tick指针每秒移动一部,移动到任务所在处,就说明该任务需要被执行了。
为应对任务设置的超时间过长,我们可以创建多个级别的滴答指针
在这里插入图片描述

这样只需要秒级时间轮60,分级60,时级24,就可以表示几乎所有的情况,如果不够可以再加年…

目前同一时刻的定时任务只能添加一个,但是我们需支持同一时刻添加多个定时任务的时间轮。
解决方案:将时间轮的一维数组设计为二维数组,让时间轮一维数组的每个节点本身也是一个数组

到了现在我们解决了对超时任务的执行,但是仍面临着一个问题,对“非活跃连接(假设30s 无通信则销毁)”的定时任务,若连接在 30s 内有数据通信,原定时销毁逻辑需延迟执行(因此时连接仍活跃,不该销毁 ),该如何来对链接任务进行延迟销毁呢?。

通过 类的析构函数 + shared_ptr 智能指针 实现定时任务的延时控制。利用智能指针的引用计数和析构时机,间接让定时任务“延迟生效”。

  • 把“任务执行逻辑”放到类的析构函数 → 类对象销毁时自动触发任务。
  • shared_ptr 管理该类对象,将 shared_ptr 放入时间轮等待调度。
  • 若等待期间有 IO 事件 → 新建 shared_ptr 替换时间轮里的旧对象 → 旧对象因引用计数未清零延迟销毁 → 任务执行被延后。

时间轮每个槽位存 shared_ptr

  • 正常流程:到 tick 时销毁 shared_ptr → 触发析构执行任务。
  • 有 IO 时:提前用新 shared_ptr 替换旧对象 → 旧对象暂时不销毁(引用计数 >0 )→ 任务延迟到新对象的 tick 执行。

在这里插入图片描述

三、代码实现

#include<iostream>
#include<functional>
#include<vector>
#include<memory>
#include<unordered_map>
#include<unistd.h>
using TaskFunc=std::function<void()>;//对类型起别名
using ReleaseFunc=std::function<void()>;
//定时任务对象
class TimerTask{
public:TimerTask(uint64_t id,uint32_t timeout,const TaskFunc&cb):_id(id),_timeout(timeout),_task_cb(cb),_cancel(false){}~TimerTask(){if(!_cancel)_task_cb(); //如果任务有效就对象销毁执行任务_release();}void SetRelsease(const ReleaseFunc &cb){_release=cb;}uint64_t TimerOut(){return _timeout;}void Cancel()  //取消定时任务{_cancel=true;}private:uint64_t _id;        //标识唯一的定时任务对象uint32_t _timeout;   //定时任务的超时时间TaskFunc _task_cb;   //要执行的定时任务bool _cancel;        //是否取消任务,false 否,true 是ReleaseFunc _release;//任务执行后删除TimerWheel中保存的定时任务对象信息};//时间轮
class TimerWheel{
public:TimerWheel():_tick(0),_capacity(60),_wheel(_capacity){}//向轮表中添加定时器任务void TimerAdd(uint64_t id,uint32_t timeout,const TaskFunc&cb){PtrTask pt(new TimerTask(id,timeout,cb));pt->SetRelsease(std::bind(&TimerWheel::RemoveTimer,this,id));int pos=(_tick+timeout)%_capacity;_wheel[pos].push_back(pt);_times[id]=WeakTask(pt);}//刷新/延时定时任务void TimerRefresh(uint64_t id){auto it=_times.find(id);if(it==_times.end()){return;//不存在定时任务,不应该刷新}PtrTask pt=it->second.lock();//获取weak_ptr中管理的shared_ptrint pos=(_tick+pt->TimerOut())%_capacity;_wheel[pos].push_back(pt);//延时定时任务}//执行超时任务void RumTimerTask(){int pos=(_tick+1)%_capacity;_tick=pos;_wheel[pos].clear();//清空该位置的数组,就会将数组中保存的shared_ptr的对象全部销毁}void TimerCancel(uint64_t id){auto it=_times.find(id);if(it==_times.end()){return;//不存在定时任务}PtrTask pt=it->second.lock();//获取weak_ptr中管理的shared_ptrpt->Cancel();//取消任务}
private://定时任务执行后将任务从wheel中移除void RemoveTimer(uint64_t id){auto it=_times.find(id);if(it!=_times.end()){_times.erase(it);}}private:using PtrTask=std::shared_ptr<TimerTask>;using WeakTask=std::weak_ptr<TimerTask>;size_t _tick;       //滴答指针,走到哪里执行哪里的任务size_t _capacity;   //轮表最大容量,也表示当前时间轮最大延时秒数std::vector<std::vector<PtrTask>>_wheel;//使用weak_ptr对定时器任务保存帮助我们对定时器任务进行刷新设置std::unordered_map<uint64_t,WeakTask> _times;
};//测试部分
// struct Test{
//     Test(){std::cout<<"构造"<<std::endl;}
//     ~Test(){std::cout<<"析构"<<std::endl;}
// };
// void DeleteTask(Test*ptr)
// {
//     delete ptr;
// }
// int main()
// {
//     Test*ptr=new Test();
//     TimerWheel wheel;
//     wheel.TimerAdd(888,5,std::bind(DeleteTask,ptr));//     for(int i=0;i<5;i++)
//     {
//         wheel.RumTimerTask();//滴答指针运行
//         wheel.TimerRefresh(888);//刷新延时任务
//         std::cout<<"延时任务刷新,在五秒后执行"<<std::endl;
//         sleep(1);
//     }
//     wheel.TimerCancel(888);
//     while(1)
//     {
//         wheel.RumTimerTask();
//         std::cout<<"*********"<<std::endl;
//         sleep(1);
//     }
//     return 0;
// }

后续使用时,只需要根据项目需要,对该模块略微调整即可

http://www.dtcms.com/a/406823.html

相关文章:

  • 边玩边学,13个Python小游戏(含源码)
  • 有了域名怎样做淘客网站中国铁建统一企业门户
  • 大连网站排名网络推广公司一个很好的个人网站开发
  • Windows文件快速检索工具:基于PyQt5的高效实现
  • C++Primerplus 编程练习 第十三章
  • Custom SRP 11 - Post Processing
  • 【Linux】进程替换
  • wordpress调用目录网址seo查询
  • 【C++】模版专题
  • K8s实践中的重点知识
  • 云栖2025 | 人工智能平台 PAI 年度发布
  • 【文献管理工具】学术研究的智能助手—Zotero 文献管理工具详细图文安装教程
  • H5平台网站建设wordpress 会话已过期
  • 建论坛网站印度人通过什么网站做国际贸易
  • UniApp ConnectSocket连接websocket
  • 正点原子【第四期】Linux之驱动开发学习笔记-5.1 设备树下的LED驱动实验
  • uniapp中全局封装一个跨组件的复制粘贴方法
  • 新奇特:神经网络烘焙坊(上),权重矩阵的甜蜜配方之谜
  • 分布式调度问题:定时任务
  • labelimg(目标检测标注工具)的安装、使用教程和问题解决
  • 【MFC】项目结构梳理
  • 中小企业声音克隆技术落地实践:痛点分析与轻量化解决方案建议
  • High precision single-photon object detection via deep neural networks,OE2024
  • 网站编程入门php做外贸网站好吗
  • 网站制作排名php自己写框架做网站
  • VMware+RockyLinux+ikuai+docker+cri-docker+k8s 自用 实践笔记(二)
  • Lambda
  • html网站开发代码公司网页设计实例教程
  • MySQL异步I/O性能优化全解析
  • SQL 执行计划解析:从 EXPLAIN 到性能优化的完整指南