深入理解定时器:优先队列与时间轮实现
文章目录
- 1. 线程池概述
- 线程池的基本特点:
- 2. 使用线程池的优先队列定时器实现
- 2.1 优先队列定时器实现
- 2.2 解释:
- 3. 使用时间轮的线程池定时器实现
- 3.1 时间轮定时器实现
- 4. 总结
在定时器设计中,使用线程池来执行定时任务可以有效提高程序的性能和可扩展性。线程池能够避免频繁创建和销毁线程的开销,并且通过重用线程来提高任务调度的效率。本文将介绍如何结合线程池和定时器来实现一个高效的定时任务调度系统。
我们将基于两种常见的定时器实现方式——优先队列 和 时间轮,使用线程池来执行定时任务,并分析每种实现方式的优缺点。
1. 线程池概述
线程池是一种管理多个线程的机制,避免了每次执行任务时创建和销毁线程的开销。线程池会预先创建一定数量的线程,并将任务提交给线程池执行。线程池中的线程会从任务队列中取出任务并执行,直到任务完成。
线程池的基本特点:
- 线程复用:线程池中的线程在任务执行完成后不会退出,而是继续等待下一个任务。
- 任务排队:任务被放入任务队列,线程池中的线程按顺序执行任务。
- 线程池大小控制:可以设置线程池的大小,避免线程过多或过少导致系统性能下降。
2. 使用线程池的优先队列定时器实现
在优先队列定时器中,使用线程池来执行定时任务,可以提高任务的执行效率。我们将在定时器任务到期时将任务提交到线程池执行,线程池会负责管理任务的执行。
2.1 优先队列定时器实现
#include <iostream>
#include <queue>
#include <functional>
#include <chrono>
#include <thread>
#include <vector>
#include <mutex>
#include <condition_variable>using namespace std;
using namespace std::chrono;// 定时任务结构体
struct TimerTask {steady_clock::time_point expiration_time; // 任务到期时间function<void()> callback; // 任务回调函数bool repeat; // 是否是周期性任务milliseconds interval; // 周期性任务间隔bool operator>(const TimerTask& other) const {return expiration_time > other.expiration_time;}
};// 线程池实现
class ThreadPool {
private:vector<thread> workers; // 线程池中的工作线程queue<function<void()>> tasks; // 任务队列mutex tasks_mutex; // 任务队列的互斥锁condition_variable cv; // 条件变量bool stop; // 是否停止线程池public:ThreadPool(size_t num_threads) : stop(false) {for (size_t i = 0; i < num_threads; ++i) {workers.emplace_back([this] {while (true) {function<void()> task;{unique_lock<mutex> lock(tasks_mutex);cv.wait(lock, [this] { return !tasks.empty() || stop; });if (stop && tasks.empty()) return; // 如果线程池被停止且任务队列为空,则退出task = move(tasks.front());tasks.pop();}task(); // 执行任务}});}}~ThreadPool() {{unique_lock<mutex> lock(tasks_mutex);stop = true;}cv.notify_all();for (thread& worker : workers) {if (worker.joinable()) {worker.join();}}}// 提交任务到线程池void submit(function<void()> task) {{unique_lock<mutex> lock(tasks_mutex);tasks.push(task);}cv.notify_one();}
};// 定时器管理类
class TimerManager {
private:priority_queue<TimerTask, vector<TimerTask>, greater<TimerTask>> task_queue; // 使用优先队列存储定时任务ThreadPool thread_pool; // 线程池bool running; // 是否正在运行定时器public:TimerManager(size_t num_threads) : thread_pool(num_threads), running(false) {}// 添加定时任务void addTask(function<void()> callback, milliseconds delay, bool repeat = false, milliseconds interval = milliseconds(0)) {TimerTask task;task.expiration_time = steady_clock::now() + delay;task.callback = callback;task.repeat = repeat;task.interval = interval;task_queue.push(task);}// 启动定时器void start() {running = true;while (running) {if (!task_queue.empty()) {TimerTask task = task_queue.top();auto now = steady_clock::now();if (now >= task.expiration_time) {task.callback(); // 执行任务task_queue.pop();// 如果是周期性任务,重新设置到期时间并将任务重新加入队列if (task.repeat) {task.expiration_time = now + task.interval;task_queue.push(task);}}}this_thread::sleep_for(milliseconds(10)); // 防止CPU占用过高}}// 停止定时器void stop() {running = false;}
};// 使用示例
int main() {TimerManager timerManager(4); // 使用4个线程的线程池// 添加一个定时任务,2秒后执行一次timerManager.addTask([]() { cout << "Task 1 executed!" << endl; }, milliseconds(2000));// 添加一个周期性任务,每3秒执行一次timerManager.addTask([]() { cout << "Periodic Task executed!" << endl; }, milliseconds(3000), true, milliseconds(3000));timerManager.start(); // 启动定时器return 0;
}
2.2 解释:
ThreadPool类实现了一个简单的线程池,任务被放入队列,工作线程从队列中取任务并执行。- 在
TimerManager中,定时任务到期时,任务会被提交到线程池执行。线程池通过并发执行任务来提高效率。 addTask方法用于添加定时任务,周期性任务会在执行完后重新加入队列。
3. 使用时间轮的线程池定时器实现
时间轮定时器使用时间轮的结构来管理定时任务,每个时间轮槽存储一些任务。当时间轮滑动时,任务会按照设定的到期时间被执行。
3.1 时间轮定时器实现
#include <iostream>
#include <vector>
#include <deque>
#include <chrono>
#include <thread>
#include <functional>
#include <mutex>
#include <condition_variable>using namespace std;
using namespace std::chrono;struct TimerTask {steady_clock::time_point expiration_time; // 任务到期时间function<void()> callback; // 任务回调函数bool operator>(const TimerTask& other) const {return expiration_time > other.expiration_time;}
};// 线程池实现
class ThreadPool {
private:vector<thread> workers;queue<function<void()>> tasks;mutex tasks_mutex;condition_variable cv;bool stop;public:ThreadPool(size_t num_threads) : stop(false) {for (size_t i = 0; i < num_threads; ++i) {workers.emplace_back([this] {while (true) {function<void()> task;{unique_lock<mutex> lock(tasks_mutex);cv.wait(lock, [this] { return !tasks.empty() || stop; });if (stop && tasks.empty()) return;task = move(tasks.front());tasks.pop();}task();}});}}~ThreadPool() {{unique_lock<mutex> lock(tasks_mutex);stop = true;}cv.notify_all();for (thread& worker : workers) {if (worker.joinable()) {worker.join();}}}void submit(function<void()> task) {{unique_lock<mutex> lock(tasks_mutex);tasks.push(task);}cv.notify_one();}
};// 时间轮定时器
class TimeWheel {
private:vector<deque<TimerTask>> slots;int current_slot;milliseconds tick_interval;ThreadPool thread_pool;public:TimeWheel(int num_slots, milliseconds interval, size_t num_threads): current_slot(0), tick_interval(interval), thread_pool(num_threads) {slots.resize(num_slots);}void addTask(function<void()> callback, milliseconds delay) {TimerTask task;task.expiration_time = steady_clock::now() + delay;int slot_index = (duration_cast<milliseconds>(task.expiration_time.time_since_epoch()) / tick_interval) % slots.size();slots[slot_index].push_back(task);}void start() {while (true) {this_thread::sleep_for(tick_interval);auto now = steady_clock::now();for (auto& task : slots[current_slot]) {if (task.expiration_time <= now) {thread_pool.submit(task.callback);}}slots[current_slot].clear();current_slot = (current_slot +1) % slots.size();}}
};// 使用示例
int main() {TimeWheel timeWheel(10, milliseconds(1000), 4); // 10个槽,每个槽代表1秒,线程池使用4个线程timeWheel.addTask([]() { cout << "Task 1 executed!" << endl; }, milliseconds(3000));timeWheel.start(); // 启动时间轮return 0;
}
4. 总结
通过使用线程池,我们能够将定时任务的执行并发化,避免每个任务都新建一个线程的性能开销。优先队列和时间轮两种实现方式各有优缺点:
- 优先队列 定时器适用于任务数量较少、任务到期时间不均匀的场景,能够较好地处理动态任务调度。
- 时间轮 定时器适用于任务数量较多且时间间隔均匀的场景,尤其在高并发任务调度时表现优越。
通过结合线程池和定时器设计,可以实现高效、可扩展的定时任务调度系统,满足各种业务需求。
提示:更多内容可以访问Clang’s Blog:https://www.clang.asia
相关文章:
深入理解定时器:优先队列与时间轮实现
文章目录 1. 线程池概述线程池的基本特点: 2. 使用线程池的优先队列定时器实现2.1 优先队列定时器实现2.2 解释: 3. 使用时间轮的线程池定时器实现3.1 时间轮定时器实现 4. 总结 在定时器设计中,使用线程池来执行定时任务可以有效提高程序的性…...
autogen-agentchat 0.4.0.dev8版本的安装
1. 安装命令 pip install autogen-agentchat0.4.0.dev8 autogen-ext[openai]0.4.0.dev82. 版本检查 import autogen_agentchat print(autogen_agentchat.__version__)0.4.0.dev8import autogen_ext print(autogen_ext.__version__)0.4.0.dev83. 第一个案例 使用 autogen-age…...
JAVA |日常开发中读写XML详解
JAVA |日常开发中读写XML详解 前言一、XML 简介二、在 Java 中读取 XML2.1 使用 DOM(Document Object Model)方式读取 XML2.2 使用 SAX(Simple API for XML)方式读取 XML 三、在 Java 中写入 XML3.1 使用 DOM 方式写入…...
React 路由与组件通信:如何实现路由参数、查询参数、state和上下文的使用
🤍 前端开发工程师、技术日更博主、已过CET6 🍨 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 🕠 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 🍚 蓝桥云课签约作者、上架课程《Vue.js 和 E…...
帮我写一篇关于AI搜索网页上编写的文章是否存在版权问题的文章, 字数在 3000 字左右。文心一言提问, 记录后用.
AI搜索网页上编写的文章是否存在版权问题? 在当今科技飞速发展的时代,AI搜索工具如雨后春笋般涌现,为人们获取信息提供了极大的便利。然而,随之而来的问题是,AI搜索案例中常常出现很多内容缺乏依据,这引发…...
电脑关机的趣味小游戏——system函数、strcmp函数、goto语句的使用
文章目录 前言一. system函数1.1 system函数清理屏幕1.2 system函数暂停运行1.3 system函数电脑关机、重启 二、strcmp函数三、goto语句四、电脑关机小游戏4.1. 程序要求4.2. 游戏代码 总结 前言 今天我们写一点稍微有趣的代码,比如写一个小程序使电脑关机…...
AttributeError: ‘DataFrame‘ object has no attribute ‘append‘的参考解决方法
文章目录 写在前面一、问题描述二、解决方法参考链接 写在前面 自己的测试环境: Ubuntu20.04 一、问题描述 运行开源的python代码的时候,遇到如下问题 AttributeError: DataFrame object has no attribute append二、解决方法 报错中的DataFrame是在…...
java垃圾回收机制介绍
Java垃圾回收机制(Garbage Collection, GC)是Java编程语言中的一项重要特性,它自动管理内存,释放不再使用的对象 1. 堆(Heap): • Java虚拟机(JVM)中用于存储对象实例的内…...
SpringMVC跨域问题解决方案
当Web应用程序尝试从一个源(例如 http://localhost:9090)向另一个不同的源(例如 http://localhost:8080)发起请求时,发现报错: 报错原因:请求被CORS策略拦截了 跨域问题概述 当Web应用程序尝试…...
【语音识别】Zipformer
Zipformer 是kaldi 团队于2024研发的序列建模模型。相比较于 Conformer、Squeezeformer、E-Branchformer等主流 ASR 模型,Zipformer 具有效果更好、计算更快、更省内存等优点。并在 LibriSpeech、Aishell-1 和 WenetSpeech 等常用数据集上取得了当时最好的 ASR 结果…...
vue+uniapp+echarts的使用(H5环境下echarts)
1.安装 npm install echarts4.9.0 --save // 带版本号 2.main.js中全局引用 // import echarts from echarts // 如果是5.0以上版本用这个 import * as echarts from echarts Vue.prototype.$echartsecharts 3.使用 <template><view id"box" style"w…...
【Python网络爬虫笔记】7-网络爬虫的搜索工具re模块
目录 一、网络爬虫中的正则表达式和re模块(一)数据提取的精确性(二)处理复杂的文本结构(三)提高数据处理效率 二、正则表达式的内涵(一)、常用元字符(二)、量…...
为什么选择 React Native 作为跨端方案
为什么选择 React Native 作为跨端方案 我深刻地知道,没有完美的跨端技术,只有适合的场景。脱离适用场景去谈跨端技术没有什么意义。 适用场景 1. 业务更新迭代较快的团队与出海团队 React Native 特别适合那些业务更新频繁、需要快速响应市场的团队…...
服务器与普通电脑有什么区别?
服务器和普通电脑(通常指的是个人计算机,即PC)有众多相似之处,主要构成包含:CPU,内存,芯片,I/O总线设备,电源,机箱及操作系统软件等,鉴于使用要求…...
Oracle 12c Data Guard 环境中的 GAP 修复方法
概述 上文中提到Oracle 12c 引入了多项新技术来简化 Data Guard 环境中的 GAP 修复过程,如(RECOVER … FROM SERVICE)。这些新特性不仅减少了操作步骤,还提高了效率和准确性。本文档将详细说明如何利用这些新特性进行 GAP 修复。…...
力扣 三角dp
动态规划基础题,当前所在元素来自上一行的两列的值。 题目 从图可以看出,每一行的第一个数与最后一个数都是1,然后中间的数是来自它左上方和右上方的数的和。当然并不是要打印这个三角形的形状,因此可以想到正常的打印方式应该是…...
SQL基础语法全解析(上篇)
一、基本概念 1. 数据库术语 数据库(database) - 保存有组织的数据的容器(通常是一个文件或一组文件)。数据表(table) - 某种特定类型数据的结构化清单。模式(schema) - 关于数据库…...
【笔记】Linux服务器端使用百度网盘
1、在python环境下,下载bypy pip install bypy 2、第一次连接需要认证 bypy info 认证通过后百度网盘会出现bypy文件夹,如下 3、查看当前文件夹下的文件 bypy list 若有很多文件夹,可在后面增加文件夹名称,列出对应位置下的文件&a…...
UEFI Spec 学习笔记---3 - Boot Manager(3)
3.2 Boot Manager Policy Protocol EFI_BOOT_MANAGER_POLICY_PROTOCOL----EFI应用程序使用该协议请求UEFI引导管理器使用平台策略连接设备。 typedef struct _EFI_BOOT_MANAGER_POLICY_PROTOCOL EFI_BOOT_MANAGER_POLICY_PROTOCOL; struct _EFI_BOOT_MANAGER_POLICY_PROTOCOL…...
ATTCK红队评估实战靶场(四)
靶机链接:http://vulnstack.qiyuanxuetang.net/vuln/detail/6/ 环境搭建 新建两张仅主机网卡,一张192.168.183.0网段(内网网卡),一张192.168.157.0网段(模拟外网网段),然后按照拓补…...
深入浅出Asp.Net Core MVC应用开发系列-AspNetCore中的日志记录
ASP.NET Core 是一个跨平台的开源框架,用于在 Windows、macOS 或 Linux 上生成基于云的新式 Web 应用。 ASP.NET Core 中的日志记录 .NET 通过 ILogger API 支持高性能结构化日志记录,以帮助监视应用程序行为和诊断问题。 可以通过配置不同的记录提供程…...
手游刚开服就被攻击怎么办?如何防御DDoS?
开服初期是手游最脆弱的阶段,极易成为DDoS攻击的目标。一旦遭遇攻击,可能导致服务器瘫痪、玩家流失,甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案,帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...
MongoDB学习和应用(高效的非关系型数据库)
一丶 MongoDB简介 对于社交类软件的功能,我们需要对它的功能特点进行分析: 数据量会随着用户数增大而增大读多写少价值较低非好友看不到其动态信息地理位置的查询… 针对以上特点进行分析各大存储工具: mysql:关系型数据库&am…...
Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)
文章目录 1.什么是Redis?2.为什么要使用redis作为mysql的缓存?3.什么是缓存雪崩、缓存穿透、缓存击穿?3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...
linux arm系统烧录
1、打开瑞芯微程序 2、按住linux arm 的 recover按键 插入电源 3、当瑞芯微检测到有设备 4、松开recover按键 5、选择升级固件 6、点击固件选择本地刷机的linux arm 镜像 7、点击升级 (忘了有没有这步了 估计有) 刷机程序 和 镜像 就不提供了。要刷的时…...
高危文件识别的常用算法:原理、应用与企业场景
高危文件识别的常用算法:原理、应用与企业场景 高危文件识别旨在检测可能导致安全威胁的文件,如包含恶意代码、敏感数据或欺诈内容的文档,在企业协同办公环境中(如Teams、Google Workspace)尤为重要。结合大模型技术&…...
JDK 17 新特性
#JDK 17 新特性 /**************** 文本块 *****************/ python/scala中早就支持,不稀奇 String json “”" { “name”: “Java”, “version”: 17 } “”"; /**************** Switch 语句 -> 表达式 *****************/ 挺好的ÿ…...
使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台
🎯 使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台 📌 项目背景 随着大语言模型(LLM)的广泛应用,开发者常面临多个挑战: 各大模型(OpenAI、Claude、Gemini、Ollama)接口风格不统一;缺乏一个统一平台进行模型调用与测试;本地模型 Ollama 的集成与前…...
Java 二维码
Java 二维码 **技术:**谷歌 ZXing 实现 首先添加依赖 <!-- 二维码依赖 --><dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.5.1</version></dependency><de…...
HashMap中的put方法执行流程(流程图)
1 put操作整体流程 HashMap 的 put 操作是其最核心的功能之一。在 JDK 1.8 及以后版本中,其主要逻辑封装在 putVal 这个内部方法中。整个过程大致如下: 初始判断与哈希计算: 首先,putVal 方法会检查当前的 table(也就…...
