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

Linux高级--3.2.4.2 常见的几种timer的设计方案

1. 使用 STL Map 管理用户定时器与 epoll_wait 配合

设计思路:

该方案的核心思想是使用 std::mapstd::multimap 来管理定时器,根据时间戳的顺序,依次添加用户注册的定时器任务。当定时器触发时,执行对应的回调函数。为了高效地管理定时器,epoll_wait 用于监听 I/O 事件,包括定时器的超时事件。

具体实现:
  1. STL Map 管理定时器:
    • 用户注册的定时器按照到期时间存储在 std::map 中。std::map 会根据时间戳自动进行排序,确保我们每次都能取出最早超时的定时器。
    • 定时器的超时时间由用户设置,一旦到达该时间点,epoll_wait 会触发超时事件。
  2. epoll_wait 配合:
    • 将当前最小超时时间的定时器加入到 epoll_wait 监听列表。
    • epoll_wait 将阻塞,直到某个定时器超时,返回事件。
    • 一旦 epoll_wait 返回,取出超时事件并执行对应的回调函数。
    • 重新设置 epoll_wait,并将下一个最小的超时时间添加到 epoll_wait 中。
优点:
  • 通过 std::map 保证定时器按时间排序,获取最小超时时间非常高效。
  • 可以很方便地扩展,支持多种不同类型的事件监听(如文件描述符的事件)。
缺点:
  • 每次添加定时器时,都需要通过 epoll_wait 更新超时时间,可能会增加额外的开销。
  • 如果定时器数量非常多,std::map 的时间复杂度为 O(log N),可能导致性能瓶颈。

这个示例代码尽量简单的展示了timer的实现思想,方便理解。真正的项目使用需要自己优化重新封装。 

#include <iostream>
#include <chrono>
#include <memory>
#include <map>
#include <string>
#include <unistd.h>
#include "libtimer.h"
#include <sys/epoll.h>
#include <pthread.h>#define OPEN_LOG#ifdef OPEN_LOG#define log(cnt, ...) fprintf(stdout, "[TIMER][%s][%d]: " cnt "\n", __func__, __LINE__, ##__VA_ARGS__)
#else#define log(cnt, ...) NULL
#endiftypedef void (*timer_handle_type)(int);struct timer_info {timer_info(int key, int ndly, timer_handle_type cb) :id(key), ndealy(ndly), fun_cb(cb) {};int id;time_t ndealy; //msbool operator<(const struct timer_info& other) const {if (ndealy < other.ndealy) return true;else if (ndealy > other.ndealy) return false;else return id < other.id; // 可以允许插入相同元素}// 添加==操作符重载bool operator==(const timer_info& other) const {return id == other.id && ndealy == other.ndealy;}void (*fun_cb)(int);timer_info() = delete;
};
int epoll_fd = -1;
std::map<timer_info, time_t> timer_map;
int g_timer_id = 0;
pthread_t g_tid = -1;static time_t GetTick() {auto sc = std::chrono::time_point_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now());auto temp = std::chrono::duration_cast<std::chrono::milliseconds>(sc.time_since_epoch());return temp.count();
}void timer_moniter(void) {while (1) {auto tmp = timer_map.begin();if (tmp == timer_map.end()) continue;struct epoll_event events[2];time_t dely = tmp->first.ndealy - GetTick();int nfds = epoll_wait(epoll_fd, events, 2, dely > 0 ? dely : 0);if (nfds == -1) {log("epoll_wait err");continue;} else if (nfds == 0) {tmp->first.fun_cb(tmp->first.id);timer_map.erase(tmp);}}return;
}int timer_create(void) {epoll_fd = epoll_create(10);if (epoll_fd == -1) {log("epoll create err");return -1;}for (auto iter : timer_map) {timer_map.erase(iter.first);}return 0;
}int timer_add(time_t ndealy, timer_handle_type cb) {timer_map.emplace(timer_info(g_timer_id, ndealy + GetTick(), cb), ndealy);log("timer size: %lu, %ld", timer_map.size(), ndealy + GetTick());return g_timer_id++;
}void timer_handle_f(int id) {log("this is the timer callbck: %d", id);return;
}int main(void) {int ret = timer_create();if (ret < 0) return 1;timer_add(100, timer_handle_f);timer_add(200, timer_handle_f);timer_add(1000, timer_handle_f);timer_moniter();
}
$ ./a.out
[TIMER][timer_add][84]: timer size: 1, 13666513
[TIMER][timer_add][84]: timer size: 2, 13666613
[TIMER][timer_add][84]: timer size: 3, 13667413
[TIMER][timer_handle_f][90]: this is the timer callbck: 0
[TIMER][timer_handle_f][90]: this is the timer callbck: 1
[TIMER][timer_handle_f][90]: this is the timer callbck: 2
^C

2. 使用 timer_fd 管理定时器与 epoll_wait 配合

设计思路:

该方案的关键是使用 timerfd_create 创建一个定时器文件描述符(timer_fd)。定时器文件描述符的超时时间与 epoll_wait 配合使用,监听定时器超时事件并执行回调函数。

具体实现:
  1. 创建定时器文件描述符:
    • 使用 timerfd_create() 创建一个 timer_fd,并设置其超时时间。
    • timerfd_settime() 用于修改定时器的超时时间。
  2. 管理定时器:
    • 用户的定时器会按照超时时间加入到一个 std::map 中,map 存储定时器的到期时间和回调函数。
    • 每次取出最小超时时间的定时器,设置到 timer_fd 中,并重新将 timer_fd 加入到 epoll 中监听。
  3. 执行回调:
    • epoll_wait 返回时,检查 timer_fd 是否触发,如果是,则执行相应的回调函数。
    • 重新设置 timer_fd,并将新的超时时间加入 epoll_wait 中,保证系统继续运行。
优点:
  • 只有一个 timer_fd 文件描述符,避免了管理多个文件描述符的复杂性。
  • 通过 timerfd 提供的原生 API,能够高效地管理定时器。
  • 更适合高频繁定时器或较小规模的定时任务。
缺点:
  • 每次需要更新 timer_fd 的超时时间,可能会导致一些系统调用开销。
  • timer_fd 只适合单个定时器或多个定时器相同的超时时间。对于大量的定时器,管理复杂度较高。
#include <iostream>
#include <chrono>
#include <memory>
#include <map>
#include <string>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/timerfd.h>
#include <sys/epoll.h>
#include <pthread.h>#define OPEN_LOG#ifdef OPEN_LOG#define log(cnt, ...) fprintf(stdout, "[TIMER][%s][%d]: " cnt "\n", __func__, __LINE__, ##__VA_ARGS__)
#else#define log(cnt, ...) NULL
#endiftypedef void (*timer_handle_type)(int);struct timer_info {timer_info(int key, int ndly, timer_handle_type cb) :id(key), ndealy(ndly), fun_cb(cb) {};int id;time_t ndealy; //msbool operator<(const struct timer_info& other) const {if (ndealy < other.ndealy) return true;else if (ndealy > other.ndealy) return false;else return id < other.id; // 可以允许插入相同元素}// 添加==操作符重载bool operator==(const timer_info& other) const {return id == other.id && ndealy == other.ndealy;}void (*fun_cb)(int);timer_info() = delete;
};
int epoll_fd = -1;
std::map<timer_info, time_t> timer_map;
int g_timer_id = 0;
int timer_fd = -1;
pthread_t g_tid = -1;static time_t GetTick() {auto sc = std::chrono::time_point_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now());auto temp = std::chrono::duration_cast<std::chrono::milliseconds>(sc.time_since_epoch());return temp.count();
}int update_timer_fd(time_t dely) {// int timerfd_gettime(int fd, struct itimerspec *curr_valuem);struct itimerspec new_value;new_value.it_interval.tv_nsec = 0;new_value.it_interval.tv_sec = 0;new_value.it_value.tv_sec =  dely / 1000;new_value.it_value.tv_nsec = (dely % 1000) * 1000 * 1000;if (new_value.it_value.tv_nsec >= 1000000000) {new_value.it_value.tv_nsec -= 1000000000;new_value.it_value.tv_sec += 1;}int ret = timerfd_settime(timer_fd, TFD_TIMER_ABSTIME, &new_value, NULL);if (ret == -1) {log("timerfd_settime err");return -1;}return 0;
}void timer_moniter(void) {while (1) {auto tmp = timer_map.begin();if (tmp == timer_map.end()) continue;struct epoll_event events[1];time_t dely = tmp->first.ndealy;update_timer_fd(dely);int nfds = epoll_wait(epoll_fd, events, 1, -1);if (nfds == 1 && events[0].data.fd == timer_fd) {tmp->first.fun_cb(tmp->first.id);timer_map.erase(tmp);} else {log("epoll_wait err");continue;}}return;
}int timer_create(void) {epoll_fd = epoll_create(10);if (epoll_fd == -1) {log("epoll create err");return -1;}timer_fd = timerfd_create(CLOCK_MONOTONIC, 0);if (timer_fd == -1) {log("timerfd_create err");return -1;}struct epoll_event ep_ev;ep_ev.data.fd = timer_fd;ep_ev.events = EPOLLIN | EPOLLET;int ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, timer_fd, &ep_ev);if (ret == -1) {log("epoll_ctl failed: %s", strerror(errno));return -1;}for (auto iter : timer_map) {timer_map.erase(iter.first);}return 0;
}int timer_add(time_t ndealy, timer_handle_type cb) {timer_map.emplace(timer_info(g_timer_id, ndealy + GetTick(), cb), ndealy);log("timer size: %lu, %ld", timer_map.size(), ndealy + GetTick());return g_timer_id++;
}void timer_handle_f(int id) {log("this is the timer callbck: %d", id);return;
}int main(void) {int ret = timer_create();if (ret < 0) return 1;timer_add(100, timer_handle_f);timer_add(200, timer_handle_f);timer_add(1000, timer_handle_f);timer_moniter();
}
$ ./a.out
[TIMER][timer_add][119]: timer size: 1, 20949087
[TIMER][timer_add][119]: timer size: 2, 20949187
[TIMER][timer_add][119]: timer size: 3, 20949987
[TIMER][timer_handle_f][125]: this is the timer callbck: 0
[TIMER][timer_handle_f][125]: this is the timer callbck: 1
[TIMER][timer_handle_f][125]: this is the timer callbck: 2
^C

3. 每个定时器都有独立的 timer_fd,并通过 epoll 监听

设计思路:

该方案是将每个定时器都创建为一个独立的 timer_fd 文件描述符,并将其与回调函数一一绑定。使用 epoll 监听这些定时器文件描述符的超时事件。当某个定时器超时时,epoll_wait 会返回事件,并通过 epoll_dataptr 字段找到对应的回调函数,执行任务。

具体实现:
  1. 每个定时器创建 timer_fd
    • 用户注册的每个定时器都会通过 timerfd_create 创建一个独立的文件描述符。
    • 每个 timer_fd 都设置自己的超时时间。
  2. epoll 配合:
    • 每个 timer_fd 文件描述符都被加入到 epoll 监听列表中。
    • 每个定时器的回调函数会通过 epoll_data.ptr 字段存储。
  3. 触发回调:
    • epoll_wait 返回时,检查各个 timer_fd 是否超时,若超时则通过 epoll_data.ptr 找到回调函数并执行。
  4. 重新设置定时器:
    • 执行完回调后,重新设置 timer_fd 的超时时间,确保定时器继续生效。
优点:
  • 每个定时器都有独立的 timer_fd,便于单独管理。
  • 高效的事件处理机制,epoll 是 Linux 高效的 I/O 多路复用机制。
缺点:
  • 当有大量定时器时,每个定时器都创建一个 timer_fd 会增加系统的资源开销。
  • 定时器数量增加时,epoll 的性能可能受到影响。

4. 时间轮(Time Wheel)设计方案

设计思路:

时间轮是一种高效的定时器管理方案,适合大量定时器的管理。其基本思想是将时间划分为多个槽,每个槽代表一个固定的时间单元。当定时器被添加时,根据定时器的超时时间将其放入相应的槽中。每当轮到该时间槽时,系统就会遍历槽中的定时器并执行回调。

具体实现:
  1. 时间轮的结构:

    • 时间轮由多个层次组成,顶层代表更长时间间隔,下一层代表更短时间间隔。
    • 每一层是一个指针数组,指向下一级时间段的任务队列。
    • 定时器的超时时间是通过 tickcount(时间轮的当前指针位置)不断推进的。
  2. 时间轮的工作原理:

    • 时间轮按“tick”的方式推进,每次推进一个单位,检查当前槽的任务是否需要执行。
    • 如果某个任务需要执行,它会被移到上一层的时间槽,并将其时间间隔减去当前时间槽的长度。
    • 通过分层设计,时间轮能够高效地管理大量定时器,避免了遍历所有定时器的开销。
  3. 定时器添加:

    • 将定时器的到期时间映射到对应的时间轮槽中,时间轮会根据当前时间指针的位置,确定该任务应该被放置在哪一层。
优点:
  • 高效管理大量定时器,避免了频繁遍历所有定时器的问题。
  • 时间轮的复杂度是常数时间 O(1),比起 std::map 需要 O(log N) 的时间复杂度要高效。
缺点:
  • 适用于定时器数量非常多且时间间隔固定的场景,对于复杂的定时器管理需求可能不够灵活。
  • 时间轮设计较为复杂,难度较高。

总结与比较:

方案优点缺点
方案 1简单易实现,利用 map 管理定时器,灵活可能存在性能瓶颈,尤其是大量定时器时
方案 2一个 timer_fd 管理所有定时器,简化管理需要频繁更新 timer_fd,性能瓶颈
方案 3每个定时器都有独立 timer_fd,精确管理定时器过多时资源消耗大
方案 4高效管理大量定时器,适合大规模定时任务设计较为复杂,难以处理复杂场景

推荐:

  • 对于低频定时器或定时器数量较少的应用,方案 1方案 2 都是不错的选择。
  • 对于高频定时器或大量定时器的情况,建议使用 方案 4,即 时间轮,能更高效地处理定时器。

0voice · GitHub


http://www.mrgr.cn/news/82049.html

相关文章:

  • Windows 环境配置 HTTPS 服务实战
  • JDK、JRE、JVM的区别
  • 前端 ——xml转json json转xml 实现 mjml 邮件内容转json,json转mjml
  • 利用python将图片转换为pdf格式的多种方法,实现批量转换,内置模板代码,全网最全,超详细!!!
  • C++软件设计模式之模板方法模式
  • 基于springboot+vue的校园论坛系统
  • Java基本操作笔记
  • 协程原理 函数栈 有栈协程
  • 有限元分析学习——Anasys Workbanch第一阶段笔记(2)应力奇异及位移结果对比、初步了解单元的基本知识
  • 大数据组件(一)快速入门调度组件Airflow
  • 人形机器人全身运动规划相关资料与文章
  • PaddleOCROCR关键信息抽取训练过程
  • 蓝桥杯(Java)(ing)
  • 【网络安全实验室】基础关实战详情
  • 沪深捉妖记(一)探寻妖股的特征
  • 数据结构与算法之动态规划: LeetCode 3105. 最长的严格递增或递减子数组 (Ts版)
  • Nginx - 整合lua 实现对POST请求的参数拦截校验(不使用Openresty)
  • L25.【LeetCode笔记】 三步问题的四种解法(含矩阵精彩解法!)
  • SAP SD信贷管理信用管理手册(下)
  • 通义千问QvQ-72B-Preview模型部署
  • FOC控制原理-ADC采样时机
  • HarmonyOS NEXT应用开发实战:免费练手的网络API接口分享
  • 数据结构与算法之动态规划: LeetCode 1143. 最长公共子序列 (Ts版)
  • 后端开发-Maven
  • 细说STM32F407单片机CAN基础知识及其HAL驱动程序
  • FPGA多路红外相机视频拼接输出,提供2套工程源码和技术支持