服务端定时器的学习(一)

article/2025/6/7 12:03:13

一、定时器

1、定时器是什么?

定时器不仅存在于硬件领域,在软件层面(客户端、网页和服务端)也普遍应用,核心功能都是高效管理大量延时任务。不同应用场景下,其实现方式和使用方法有所差异。

2、定时器解决了什么问题?

可以定期清理缓存,定时备份数据,在服务器负载较大时,自动执行一些重要的功能,提高服务器的效率和稳定性。

3、是怎么解决的?

  • 组织大量延时任务的数据结构(容器)
  • 触发最近将超时的任务的机制

4、实现方式:

  • 对任务按触发时间进行排序:红黑树(map,set,multimap,multiset)–nginx,最小堆–libevent,libev,go,应用在单线程场景下
    • 1、触发时刻作为key,任务作为val
    • 2、快速找到最近要超时的任务
    • 3、触发后要删除该任务且支持随时删除任务
    • 4、允许相同时刻触发任务
  • 对执行顺序进行组织:时间轮,针对当前时间指针做偏移。–netty,skynet,kafka,应用在多线程场景下

5、有哪些常用触发机制?

  • I/O多路复用的最后一个超时参数
  • 将定时器转化为io处理,timerfd

6、使用场景

  • 与网络模块协同处理
  • 基于事件驱动业务开展
  • 除了协同网络处理,复用系统调用

二、具体实现

1、采用红黑树,对任务按触发时间进行排序

  • map<key, value>: 以key存触发时间,value存任务,那么可能存在多个同一时刻的任务,不选
  • multimap<key, value>:可能存储重复的值,然后操作起来比较麻烦,不选
  • set: 不可能出现重复的,可以
    那么以一个自定义结构作为key值进行存储,并且按触发时间进行排序
typedef struct TimerNode_S{time_t expire;//过期时间uint64_t id;    //由于可能存在多个定时器在同一时间过期,所以需要一个唯一标识}TimerNode_S;bool operator < (const TimerNode_S& lhd, const TimerNode_S& rhd)
{if(lhd.expire < rhd.expire){return true;}else if(lhd.expire > rhd.expire){return false;}else{      //如果相等,谁先插入,谁就先执行return lhd.id < rhd.id;}
}set<TimerNode, less<>> timeouts;

2、计算最近触发的定时任务离当前还有多久?

time_t TimeOut() 
{auto iter = timeouts.begin();if (iter == timeouts.end()) {return -1;}time_t t = iter->expire - GetTick();return t > 0 ? t : 0;
}

3、获取当前时间

/**
* @brief 获取当前时间的时间戳(以毫秒为单位)
*
* 使用 std::chrono::steady_clock 获取从系统启动到当前的时间
* std::chrono::system_clock,受系统时间影响,可能会被修改
*
* @return 返回当前时间的时间戳(以毫秒为单位)
*/
static inline time_t GetTick()                  
{return chrono::duration_cast<chrono::milliseconds>(chrono::steady_clock::now().time_since_epoch()).count();   
}

4、添加定时器

/**
* @brief 添加定时器
*
* 将一个定时器节点添加到定时器列表中,并返回该节点的标识。
*
* @param msec 定时器超时时间,单位为毫秒
* @param cb 定时器超时时执行的回调函数
*
* @return 返回定时器的标识,类型为 TimerNode_S
*/
TimerNode_S AddTimer(int msec, TimerNode::Callback cb)
{time_t expire = GetTick() + msec;       //过期时间if(timeouts.empty() || expire <= timeouts.crbegin()->expire){auto pairs = timeouts.emplace(GetID(), expire, move(cb));return static_cast<TimerNode_S>(*pairs.first);}auto ele = timeouts.emplace_hint(timeouts.crbegin().base(), GetID(), expire, move(cb));return static_cast<TimerNode_S>(*ele);
}

5、删除定时器

/**
* @brief 删除定时器节点
*
* 从定时器集合中删除指定的定时器节点。
*
* @param node 需要删除的定时器节点
*/
void DelTimer(TimerNode_S& node)
{auto iter = timeouts.find(node);if(iter != timeouts.end()){timeouts.erase(iter);}
}

6、处理定时器任务

/**
* @brief 处理超时事件
*
* 该函数遍历超时事件列表,对于已超时的每个事件,调用其回调函数进行处理,并从列表中移除该事件。
*
* @param now 当前时间戳
*/
void HandleTimeout(time_t now)
{auto iter = timeouts.begin();while(iter != timeouts.end() && iter->expire <= now){iter->cb(*iter);iter = timeouts.erase(iter);}
}struct epoll_event evs[64] = {0};
while(true){int n = epoll_wait(epfd, evs, 64, timer->TimeOut());time_t now = CTimer::GetTick();for(int j = 0; j < n; ++j){cout<<"epoll_wait:"<<endl;}timer->HandleTimeout(now);
}

在这里插入图片描述

以上是采用I/O多路复用的最后一个超时参数,接下来更换成timerfd

7、主要调用的函数

/*
*功能:创建定时器
*clockfd: CLOCK_REALTIME-系统实时时钟,与系统时间同步,受用户手动修改时间影响。CLOCK_MONOTONIC-单调递增时钟,自系统启动以来的时间,不受系统时间调整的影响
*flags:TFD_NONBLOCK(非阻塞模式)和 TFD_CLOEXEC(在 exec 调用时自动关闭文件描述符)          
*
*/
timerfd_create(int clockfd, int flags);/*
* 功能:用于设置定时器的初始超时时间和后续周期时间
* fd:timerfd
* flags: 控制定时器行为的标志:
*       0-------绝对时间
*       TFD_TIMER_ABSTIME------表示相对时间
* new_value: 指定了定时器的初始超时时间和(可选的)后续周期时间
* old_value: 用于恢复定时器到之前的状态,常设为nullptr
*/
int timerfd_settime(int fd, int flags, const struct itimerspec *new_value, struct itimerspec *old_value);

8、将之前的计算触发时间的方式改成timerfd

void UpdateTimerfd(const int fd) {struct timespec abstime;auto iter = timeouts.begin();if (iter != timeouts.end()) {abstime.tv_sec = iter->expire / 1000;abstime.tv_nsec = (iter->expire % 1000) * 1000000;} else {abstime.tv_sec = 0;abstime.tv_nsec = 0;}struct itimerspec its = {.it_interval = {},.it_value = abstime};timerfd_settime(fd, TFD_TIMER_ABSTIME, &its, nullptr);
}int tfd = timerfd_create(CLOCK_MONOTONIC, 0);struct epoll_event evs[64] = {0};
while(true){timer->UpdateTimerfd(tfd);int n = epoll_wait(epfd, evs, 64, -1);time_t now = CTimer::GetTick();for(int j = 0; j < n; ++j){cout<<"epoll_wait:"<<endl;}timer->HandleTimeout(now);
}

在这里插入图片描述

三、总结

  • 定时器在程序中无处不在,无论是硬件,还是网页,客户端,服务端等。
  • 在服务端上合理地使用定时器,能提高服务器的效率和稳定性,如定时清理缓存,在服务器高负载情况下,自动执行一些重要的任务等。
  • 定时器的数据结构多种多样,有根据触发时间排序的红黑树,最小堆,也有根据执行顺序的时间轮。
  • 服务端常与网络模块协同处理
  • 服务端常基于事件驱动业务开展
  • 服务端除了协同网络处理,复用系统调用

代码:
Code


http://www.hkcw.cn/article/gNwlkPxUnn.shtml

相关文章

Axure形状类组件图标库(共8套)

点击下载《月下倚楼图标库(形状组件)》 原型效果&#xff1a;https://axhub.im/ax9/02043f78e1b4386f/#g1 摘要 本图标库集锦精心汇集了8套专为Axure设计的形状类图标资源&#xff0c;旨在为产品经理、UI/UX设计师以及开发人员提供丰富多样的设计素材&#xff0c;提升原型设计…

CET6 仔细阅读 24年12月第一套-C1 大脑这一块

文章 There are hundreds of personality quizzes online that assert they can ascertain whether the right or left half of your brain is dominant. Left-brained people are supposedly logical and excel at language and math while right- brained people are more i…

【JavaWeb】SpringBoot原理

1 配置优先级 在前面&#xff0c;已经学习了SpringBoot项目当中支持的三类配置文件&#xff1a; application.properties application.yml application.yaml 在SpringBoot项目当中&#xff0c;我们要想配置一个属性&#xff0c;通过这三种方式当中的任意一种来配置都可以&a…

硬件工程师笔记——555定时器应用Multisim电路仿真实验汇总

目录 一 555定时器基础知识 二、引脚功能 三、工作模式 1. 单稳态模式&#xff1a; 2. 双稳态模式&#xff08;需要外部电路辅助&#xff09;&#xff1a; 3. 无稳态模式&#xff08;多谐振荡器&#xff09;&#xff1a; 4. 可控脉冲宽度调制&#xff08;PWM&#xff09…

基于springboot的图书管理系统的设计与实现

其他源码获取可以看首页&#xff1a;代码老y 个人简介&#xff1a;专注于毕业设计项目定制开发&#xff1a;springbootvue系统&#xff0c;Java微信小程序&#xff0c;javaSSM系统等技术开发&#xff0c;并提供远程调试部署、代码讲解、文档指导、ppt制作等技术指导。源码获取&…

一个html实现数据库自定义查询

使用场景 应用上线后甲方频繁的找开发查询数据库数据&#xff0c;且没有固定的查询规律&#xff0c;产品经理也没有规划报表需求。 实现方案 后端开放自定义sql查询&#xff0c;屏蔽所有数据库的高危操作&#xff0c;将常用查询的sql放在一个html中的js中直接查询&#xff0…

[特殊字符] Unity UI 性能优化终极指南 — ScrollRect篇

ScrollRect ManualScrollRect API 我参考了官方最新文档&#xff08;基于UGUI 3.0包&#xff09;&#xff0c;加上实际性能测试经验&#xff0c;直接给你梳理&#xff1a; &#x1f3af; Unity UI 性能优化终极指南 — ScrollRect篇 &#x1f9e9; 什么是 ScrollRect&#xff…

VsCode 安装 Cline 插件并使用免费模型(例如 DeepSeek)

当前时间为 25/6/3&#xff0c;Cline 版本为 3.17.8 点击侧边栏的“扩展”图标 在搜索框中输入“Cline” 找到 Cline 插件&#xff0c;然后点击“安装” 安装完成后&#xff0c;Cline 图标会出现在 VS Code 的侧边栏中 点击 Use your own API key API Provider 选择 OpenRouter…

Spark期末基础复习

填空选择判断 第一章 一、填空题 1.Scala语言的特性包含____________、函数式编程的、____________、可扩展的、____________。 2.在Scala 数据类型层级结构的底部有两个数据类型&#xff0c;分别是____________和____________。 3.在Scala中&#xff0c;声明变量的关键字有…

【Zephyr 系列 5】定时器与低功耗控制:打造省电高效的嵌入式系统

🧠关键词:Zephyr、定时器、k_timer、PM、低功耗、STM32、RTC 📌适合人群:想实现周期任务、功耗优化、定时唤醒等功能的 MCU 工程师 ✨ 前言:省电不是选项,而是刚需 在电池供电的嵌入式设备中,功耗决定了产品寿命与可靠性。无论是蓝牙传感器、GPS 跟踪器还是 LoRa 节点…

ATR2660SGNSS L1 频段 低噪声放大器

ATR2660S是一款应用于GNSS接收机的低噪声放大器&#xff0c;芯片集成了有源偏置电路和ESD保护电路&#xff0c;核心电路部分使用两级放大器结构进一步提高功率增益。 主要特征 高增益&#xff1a; 30dB 1575.42MHz 31dB 1176.45MHz 低噪声系数&#xff1a; 1.1dB 1575.42MHz 1.…

【Spring AI 1.0.0】Spring AI 1.0.0框架快速入门(1)——Chat Client API

Spring AI框架快速入门 一、前言二、前期准备2.1 运行环境2.2 maven配置2.3 api-key申请 三、Chat Client API3.1 导入pom依赖3.2 配置application.properties文件3.3 创建 ChatClient3.3.1 使用自动配置的 ChatClient.Builder3.3.2 使用多个聊天模型 3.4 ChatClient请求3.5 Ch…

FreeRTOS的简单介绍

一、FreeRTOS介绍 FreeRTOS并不是实时操作系统&#xff0c;因为它是分时复用的 利用CubeMX快速移植 二、快速移植流程 1. 在 SYS 选项里&#xff0c;将 Debug 设为 Serial Wire &#xff0c;并且将 Timebase Source 设为 TIM2 &#xff08;其它定时器也行&#xff09;。为何…

Deepseek/cherry studio中的Latex公式复制到word中

需要将Deepseek/cherry studio中公式复制到word中&#xff0c;但是deepseek输出Latex公式&#xff0c;比如以下Latex代码段&#xff0c;需要通过Mathtype翻译才能在word中编辑。 $$\begin{aligned}H_1(k1) & H_1(k) \frac{1}{A_1} \left( Q_1 u_1(k) Q_{i1} - Q_2 u_2(k…

如何爬取google应用商店的应用分类呢?

以下是爬取Google Play商店应用包名(package name)和对应分类的完整解决方案&#xff0c;采用ScrapyPlaywright组合应对动态渲染页面&#xff0c;并处理反爬机制&#xff1a; 完整爬虫实现 1. 安装必要库 # 卸载现有安装pip uninstall playwright scrapy-playwright -y# 重新…

英福康INFICON VGC501, VGC502, VGC503 单通道、双通道和三通道测量装置

英福康INFICON VGC501, VGC502, VGC503 单通道、双通道和三通道测量装置

C++ Visual Studio 2017厂商给的源码没有.sln文件 易兆微芯片下载工具加开机动画下载。

1.先用Visual Studio 2017打开Yichip YC31xx loader.vcxproj&#xff0c;再用Visual Studio 2022打开。再保侟就有.sln文件了。 易兆微芯片下载工具加开机动画下载 ExtraDownloadFile1Info.\logo.bin|0|0|10D2000|0 MFC应用兼容CMD 在BOOL CYichipYC31xxloaderDlg::OnIni…

JsonCpp 库如何集成到Visual studio

这是用于 在 C 中解析和生成 JSON 数据 的工具包&#xff0c;适合在需要与前端、网络、配置等 JSON 格式交互的 C 项目中使用。 Json&#xff08;基于JsonCpp&#xff09; 格式全称&#xff1a;JavaScript Object Notation 格式特点&#xff1a; 与开发语言无关轻量级的数据…

sourceinsight4.0不识别.cc解决办法

options—>preferences 选择 c language, 点击右边的 file types, 添加 ;*.cc即可 重新创建工程, 不仅有.cc, 还有.cc的目录结构

RNN结构扩展与改进:从简单循环网络到时间间隔网络的技术演进

本文系统介绍 RNN 结构的常见扩展与改进方案。涵盖 简单循环神经网络&#xff08;SRN&#xff09;、双向循环神经网络&#xff08;BRNN&#xff09;、深度循环神经网络&#xff08;Deep RNN&#xff09; 等多种变体&#xff0c;解析其核心架构、技术特点及应用场景&#xff0c;…