网易面试:手撕定时器

概述:

本文使用STL容器-set以及Linux提供的timerfd来实现定时器组件

所谓定时器就是管理大量定时任务,使其能按照超时时间有序地被执行

需求分析:

1.数据结构的选择:存储定时任务

2.驱动方式:如何选择一个任务并执行

一、数据结构的选择:红黑树

红黑树是一种查找、删除、插入时间复杂度为O(logN)的数据结构,性能均衡,STL的set和map就是基于红黑树实现的,与普通的红黑树不同,STL的红黑树设计添加了指向最大节点和最小节点的指针,这一点实现了set和map可以使用O(1)的时间复杂度查找最大值和最小值:

在这里插入图片描述

二、驱动方式:timerfd + epoll

Linux提供了定时机制timerfd,与sockfd一样,内核负责检测该文件描述符的就绪情况,并需要epoll等io多路复用机制向用户层通知

三、代码实现

1.定时任务节点:

struct TimerNodeBase { //  定时器节点基类,用于红黑树(set)存储time_t expire;     //  超时时间uint64_t id;       //  唯一 id, 用于解决超时时间相同的节点存储问题
};struct TimerNode : public TimerNodeBase {  // 子类定时器节点, 添加了一个回调函数using Callback = function<void(const TimerNode &node)>;Callback func;TimerNode(int64_t id, time_t expire, Callback func) : func(std::move(func)) { // 使用 move 右值引用,性能高this->expire = expire;this->id = id;}
};

这里设计两个结构体的目的是将回调函数和其它属性(超时时间和id)隔离开

2.运算符重载,用于比较两个节点的大小(超时时间大小)

bool operator < (const TimerNodeBase &lhd, const TimerNodeBase &rhd) { // 运算符重载,比较两个节点的大小// 先根据超时时间判定大小if (lhd.expire < rhd.expire) {return true;} else if (lhd.expire > rhd.expire) {return false;} // 超时时间相同时,根据 id 判断大小else return lhd.id < rhd.id;
}

3.定时器类:

class Timer {public:static inline time_t GetTick() { // 获取系统当前时间戳return chrono::duration_cast<chrono::milliseconds>(chrono::steady_clock::now().time_since_epoch()).count();}TimerNodeBase AddTimer(int msec, TimerNode::Callback func) {time_t expire = GetTick() + msec; // msec是相对超时时间,expire是绝对超时时间(时间戳)// 如果待插入节点当前不是红黑树中最大的if (timeouts.empty() || expire <= timeouts.crbegin()->expire) { auto pairs = timeouts.emplace(GenID(), expire, std::move(func)); // emplace是在容器内部生成一个对象并插入到红黑树中,性能优于push的copy操作  2.使用move右值引用,避免copy// 使用static_cast将子类cast成基类return static_cast<TimerNodeBase>(*pairs.first); // emplace的返回值pair包含:1.创建并插入的节点 2.是否成功插入(已存在相同节点则插入失败)}// 如果待插入节点是最大的,直接插入到最右侧,时间复杂度 O(1) ,优化性能auto ele = timeouts.emplace_hint(timeouts.crbegin().base(), GenID(), expire, std::move(func));// 返回基类而不是子类return static_cast<TimerNodeBase>(*ele);}void DelTimer(TimerNodeBase &node) { // 从(set)红黑树中删除一个节点auto iter = timeouts.find(node); // 找到指定节点if (iter != timeouts.end())timeouts.erase(iter);       // 移除}void HandleTimer(time_t now) {     // 执行当前已超时的任务auto iter = timeouts.begin();while (iter != timeouts.end() && iter->expire <= now) {iter->func(*iter);iter = timeouts.erase(iter); // eraser返回下一个节点}}public:// 更新 timerfd 的到期时间为 timeouts 集合中最早到期的定时器时间virtual 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;its.it_interval = {};its.it_value = abstime;timerfd_settime(fd, TFD_TIMER_ABSTIME, &its, nullptr);}private:static inline uint64_t GenID() { // 生成一个 idreturn gid++;}static uint64_t gid; // 全局 id 变量set<TimerNode, std::less<> > timeouts; // less指定排序方式:从小到大
};

在class timer中实现了

1.创建set容器对象

2.定时任务节点的添加

3.定时任务节点的删除

4.执行已超时任务

四、main函数

int main() {int epfd = epoll_create(1);  // epollint timerfd = timerfd_create(CLOCK_MONOTONIC, 0);struct epoll_event ev = {.events = EPOLLIN | EPOLLET};epoll_ctl(epfd, EPOLL_CTL_ADD, timerfd, &ev);unique_ptr<Timer> timer = make_unique<Timer>();int i = 0;timer->AddTimer(1000, [&](const TimerNode &node) {      //   lamda 表达式cout << Timer::GetTick() << "node id:" << node.id << "revoked times" << ++i << endl;});timer->AddTimer(1000, [&](const TimerNode &node) {cout << Timer::GetTick() << " node id:" << node.id << " revoked times:" << ++i << endl;});timer->AddTimer(3000, [&](const TimerNode &node) {cout << Timer::GetTick() << " node id:" << node.id << " revoked times:" << ++i << endl;});auto node = timer->AddTimer(2100, [&](const TimerNode &node) {cout << Timer::GetTick() << " node id:" << node.id << " revoked times:" << ++i << endl;});timer->DelTimer(node);cout << "now time:" << Timer::GetTick() << endl;struct epoll_event evs[64] = {0};while (true) {timer->UpdateTimerfd(timerfd);    // epoll中timerfd的到期时间int n = epoll_wait(epfd, evs, 64, -1); // 内核检测定时时间timerfdtime_t now = Timer::GetTick();   // 当前系统时间戳for (int i = 0; i < n; i++) {     // for network event handle}timer->HandleTimer(now);         // 处理现在到期的定时任务}epoll_ctl(epfd, EPOLL_CTL_DEL, timerfd, &ev);close(timerfd);close(epfd);return 0;
}

timerfd的使用:

1.将timerfd添加到epoll中

2.使用timerfd_settime()函数更新timerfd的超时时间

3.当超时时间到达时,epoll会通知事件

4.执行完到期的任务后,更新timerfd的超时时间为红黑树中最小节点的超时时间,epoll会再次进行通知

五、完整代码

#include <sys/epoll.h>
#include <sys/timerfd.h>
#include <time.h>
#include <unistd.h>#include <functional>
#include <chrono>
#include <set>
#include <memory>
#include <iostream>using namespace std;struct TimerNodeBase { //  定时器节点基类,用于红黑树(set)存储time_t expire;     //  超时时间uint64_t id;       //  唯一 id, 用于解决超时时间相同的节点存储问题
};struct TimerNode : public TimerNodeBase {  // 子类定时器节点, 添加了一个回调函数using Callback = function<void(const TimerNode &node)>;Callback func;TimerNode(int64_t id, time_t expire, Callback func) : func(std::move(func)) { // 使用 move 右值引用,性能高this->expire = expire;this->id = id;}
};bool operator < (const TimerNodeBase &lhd, const TimerNodeBase &rhd) { // 运算符重载,比较两个节点的大小// 先根据超时时间判定大小if (lhd.expire < rhd.expire) {return true;} else if (lhd.expire > rhd.expire) {return false;} // 超时时间相同时,根据 id 判断大小else return lhd.id < rhd.id;
}class Timer {public:static inline time_t GetTick() { // 获取系统当前时间戳return chrono::duration_cast<chrono::milliseconds>(chrono::steady_clock::now().time_since_epoch()).count();}TimerNodeBase AddTimer(int msec, TimerNode::Callback func) {time_t expire = GetTick() + msec; // msec是相对超时时间,expire是绝对超时时间(时间戳)// 如果待插入节点当前不是红黑树中最大的if (timeouts.empty() || expire <= timeouts.crbegin()->expire) { auto pairs = timeouts.emplace(GenID(), expire, std::move(func)); // emplace是在容器内部生成一个对象并插入到红黑树中,性能优于push的copy操作  2.使用move右值引用,避免copy// 使用static_cast将子类cast成基类return static_cast<TimerNodeBase>(*pairs.first); // emplace的返回值pair包含:1.创建并插入的节点 2.是否成功插入(已存在相同节点则插入失败)}// 如果待插入节点是最大的,直接插入到最右侧,时间复杂度 O(1) ,优化性能auto ele = timeouts.emplace_hint(timeouts.crbegin().base(), GenID(), expire, std::move(func));// 返回基类而不是子类return static_cast<TimerNodeBase>(*ele);}void DelTimer(TimerNodeBase &node) { // 从(set)红黑树中删除一个节点auto iter = timeouts.find(node); // 找到指定节点if (iter != timeouts.end())timeouts.erase(iter);       // 移除}void HandleTimer(time_t now) {     // 执行当前已超时的任务auto iter = timeouts.begin();while (iter != timeouts.end() && iter->expire <= now) {iter->func(*iter);iter = timeouts.erase(iter); // eraser返回下一个节点}}public:// 更新 timerfd 的到期时间为 timeouts 集合中最早到期的定时器时间virtual 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;its.it_interval = {};its.it_value = abstime;timerfd_settime(fd, TFD_TIMER_ABSTIME, &its, nullptr);}private:static inline uint64_t GenID() { // 生成一个 idreturn gid++;}static uint64_t gid; // 全局 id 变量set<TimerNode, std::less<> > timeouts; // less指定排序方式:从小到大
};uint64_t Timer::gid = 0;int main() {int epfd = epoll_create(1);  // epollint timerfd = timerfd_create(CLOCK_MONOTONIC, 0);struct epoll_event ev = {.events = EPOLLIN | EPOLLET};epoll_ctl(epfd, EPOLL_CTL_ADD, timerfd, &ev);unique_ptr<Timer> timer = make_unique<Timer>();int i = 0;timer->AddTimer(1000, [&](const TimerNode &node) {      //   lamda 表达式cout << Timer::GetTick() << "node id:" << node.id << "revoked times" << ++i << endl;});timer->AddTimer(1000, [&](const TimerNode &node) {cout << Timer::GetTick() << " node id:" << node.id << " revoked times:" << ++i << endl;});timer->AddTimer(3000, [&](const TimerNode &node) {cout << Timer::GetTick() << " node id:" << node.id << " revoked times:" << ++i << endl;});auto node = timer->AddTimer(2100, [&](const TimerNode &node) {cout << Timer::GetTick() << " node id:" << node.id << " revoked times:" << ++i << endl;});timer->DelTimer(node);cout << "now time:" << Timer::GetTick() << endl;struct epoll_event evs[64] = {0};while (true) {timer->UpdateTimerfd(timerfd);    // epoll中timerfd的到期时间int n = epoll_wait(epfd, evs, 64, -1); // 内核检测定时时间timerfdtime_t now = Timer::GetTick();   // 当前系统时间戳for (int i = 0; i < n; i++) {     // for network event handle}timer->HandleTimer(now);         // 处理现在到期的定时任务}epoll_ctl(epfd, EPOLL_CTL_DEL, timerfd, &ev);close(timerfd);close(epfd);return 0;
}

推荐学习 https://xxetb.xetslk.com/s/p5Ibb

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/17389.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CSS,HTML,JS 以及Vue前端面试题八股文总结【看完你就变高手】

■ 符号说明 &#x1f498; 主题 &#x1f31f; 常见重要 &#x1f31b; 需要有印象的 &#x1f195; v3新特性 ■ 杂谈 &#x1f31b; SEO优化 合理的title、description、keywords&#xff1a;搜索对着三项的权重逐个减小&#xff0c;title值强调重点即可&#xff1b;descrip…

东软的第三个研发基地,为什么选择了武汉?

继沈阳、大连之后&#xff0c;东软集团在国内打造的第三个研发基地——武汉东软软件园&#xff0c;于2024年5月25日正式开园。 “占地面积158亩、建筑面积14万余平方米的武汉东软软件园&#xff0c;从开工到竣工仅仅用了18个月的时间。这样的建设速度&#xff0c;充分体现了武汉…

2.开发环境介绍

开发环境介绍三种&#xff1a;第一种是在线开发环境、第二种是Windows下的开发环境、第三种是Linux下的开发环境。 1.在线开发环境 2.Windows下的开发环境 用的比较多的是Devc&#xff0c;新手适合使用&#xff0c;上手快&#xff0c;简单&#xff0c;方便。 Devc使用&#x…

蓝桥杯练习系统(算法训练)ALGO-932 低阶行列式计算

资源限制 内存限制&#xff1a;64.0MB C/C时间限制&#xff1a;1.0s Java时间限制&#xff1a;3.0s Python时间限制&#xff1a;5.0s 问题描述 给出一个n阶行列式(1<n<9)&#xff0c;求出它的值。 输入格式 第一行给出两个正整数n,p&#xff1b;   接下来n行&…

【JavaScript】P2 JavaScript 书写位置

本博文总结&#xff1a; JavaScript 书写位置&#xff1a; 内部外部行内 注意事项&#xff1a; 书写的位置尽量写到 </body> 之前外部 js 标签中间不写任何内容&#xff0c;因为不予以展示 正文&#xff1a; 交互效果示例 一个简单的交互效果示例&#xff1b; <…

【从零开始学习RabbitMQ | 第一篇】如何确保生产者的可靠性

目录 前言&#xff1a; 生产者重连机制&#xff1a; 生产者确认机制&#xff1a; Publisher Confirm&#xff08;生产者者确认&#xff09; Publish Return&#xff08;发布返回&#xff09; 总结&#xff1a; 前言&#xff1a; 在现代的分布式系统中&#xff0c;消息队…

【NumPy】关于numpy.divide()函数,看这一篇文章就够了

&#x1f9d1; 博主简介&#xff1a;阿里巴巴嵌入式技术专家&#xff0c;深耕嵌入式人工智能领域&#xff0c;具备多年的嵌入式硬件产品研发管理经验。 &#x1f4d2; 博客介绍&#xff1a;分享嵌入式开发领域的相关知识、经验、思考和感悟&#xff0c;欢迎关注。提供嵌入式方向…

zabbix监控mysql

一、mysql数据库监控的内容有 mysql的吞吐量 mysql的常规操作&#xff08;增删改查&#xff09; QPS&#xff08;Questions Per second:&#xff09;每秒能处理多少次请求数 TPS&#xff08;Transactions Per Second&#xff09;每秒查询处理的事务数 mysql库大小和表大小 监控…

欧科云链:Web3.0时代 具备链上数据分析能力的公司愈发凸显其价值

在当今激烈的市场竞争中&#xff0c;新兴互联网领域迅速崛起&#xff0c;Web2.0已相对成熟&#xff0c;用户创造数据&#xff0c;但不拥有数据。被视为新的价值互联网的Web3.0&#xff0c;赋予用户真正的数据自主权&#xff0c;它的到来被认为是打破Web2.0垄断的机遇。 在Web3…

迅狐跨境商城系统源码

在当今全球化的商业环境中&#xff0c;跨境电商的兴起为商家提供了无限的可能性。为了满足这一需求&#xff0c;跨境商城系统源码的开发显得尤为重要。本文将探讨跨境商城系统源码的优势&#xff0c;以及如何利用这些优势来构建一个成功的跨境电商平台。 独立开发&#xff0c;…

LLM 大模型学习必知必会系列(十三):基于SWIFT的VLLM推理加速与部署实战

LLM 大模型学习必知必会系列(十三)&#xff1a;基于SWIFT的VLLM推理加速与部署实战 1.环境准备 GPU设备: A10, 3090, V100, A100均可. #设置pip全局镜像 (加速下载) pip config set global.index-url https://mirrors.aliyun.com/pypi/simple/ #安装ms-swift pip install ms-…

windows11如何安装IIS

目录 IIS是什么&#xff1f; 为什么要配置IIS&#xff1f; 1.打开控制面板进入程序 2.点击启用或者关闭windos功能 3.勾选IIS相关的web项 4.点击确定等待一分钟程序变更即可 5.主页搜索internet 点击进入 6.进入IIS进行查看配置&#xff0c;并测试&#xff0c;也可以浏…

重学java 49 List接口

但逢良辰&#xff0c;顺颂时宜 —— 24.5.28 一、List接口 1.概述: 是collection接口的子接口 2.常见的实现类: ArrayList LinkedList Vector 二、List集合下的实现类 1.ArrayList集合的使用及源码分析 1.概述 ArrayList是List接口的实现类 2.特点 a.元素有序 —> 按照什么顺…

常见SSL证书品牌关系图

常见SSL证书品牌关系图 在SSL证书市场上&#xff0c;有几个主要的品牌和他们之间的复杂关系。以下是一些主要的SSL证书提供商及其关系的简要概述&#xff1a; DigiCert&#xff1a; DigiCert 是最大的SSL证书颁发机构之一。它收购了Symantec的SSL和PKI业务&#xff0c;其中包括…

深度合作!博睿数据联合中国信通院开展公网服务质量评估工作!

近日&#xff0c;中国信息通信研究院&#xff08;简称“中国信通院”&#xff09;算网质量保障工作全面启动&#xff0c;博睿数据&#xff08;bonree.com&#xff0c;股票代码688229&#xff09;作为信通院算网质量测试独家技术支持单位&#xff0c;提供公网服务质量测评整体解…

Linux之多进程

文章目录 c程序获取进程pid和ppid进程相关命令进程的创建多进程进程退出exit()函数_exit函数 进程的等待wait函数waitpid函数 进程的替换进程间的通信一、无名管道二、有名管道三、信号kill函数raise函数pause() 函数自定义信号处理函数SIGALARM信号子进程退出信号SIGCHLD 四、…

基于mybatis-plus的多语言扩展

概览 对于表中字段&#xff0c;需要实现多语言的方案探讨&#xff1a; 1.表中横向扩展多个字段分别存储中文&#xff0c;英文&#xff0c;俄语等语言字段&#xff0c;查询时&#xff0c;根据需要查询的语言&#xff0c;进行查询 2.增加一张多语言表&#xff0c;存储多语言信…

IC开发——VCS基本用法

1. 简介 VCS是编译型verilog仿真器&#xff0c;处理verilog的源码过程如下&#xff1a; VCS先将verilog/systemverilog文件转化为C文件&#xff0c;在linux下编译链接生成可执行文件&#xff0c;在linux下运行simv即可得到仿真结果。 VCS使用步骤&#xff0c;先编译verilog源…

STM32--ADC

一、简介 *ADC&#xff08;Analog-Digital Converter&#xff09;模拟-数字转换器 *ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量&#xff0c;建立模拟电路到数字电路的桥梁 *12位逐次逼近型ADC&#xff0c;1us转换时间 *输入电压范围&#xff1a;0~3.3V&…

redisson 释放分布式锁 踩坑

java.lang.IllegalMonitorStateException: attempt to unlock lock, not locked by current thread by node id: 48c213c9-1945-4c1b-821e-6d32e347eb44 thread-id: 69 出错代码&#xff1a; private void insertHourLog(Timestamp lastHourStartTimeStamp) {RLock lock red…