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

深入理解定时器:优先队列与时间轮实现

文章目录

      • 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. 线程池概述线程池的基本特点&#xff1a; 2. 使用线程池的优先队列定时器实现2.1 优先队列定时器实现2.2 解释&#xff1a; 3. 使用时间轮的线程池定时器实现3.1 时间轮定时器实现 4. 总结 在定时器设计中&#xff0c;使用线程池来执行定时任务可以有效提高程序的性…...

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 &#xff5c;日常开发中读写XML详解 前言一、XML 简介二、在 Java 中读取 XML2.1 使用 DOM&#xff08;Document Object Model&#xff09;方式读取 XML2.2 使用 SAX&#xff08;Simple API for XML&#xff09;方式读取 XML 三、在 Java 中写入 XML3.1 使用 DOM 方式写入…...

React 路由与组件通信:如何实现路由参数、查询参数、state和上下文的使用

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…...

帮我写一篇关于AI搜索网页上编写的文章是否存在版权问题的文章, 字数在 3000 字左右。文心一言提问, 记录后用.

AI搜索网页上编写的文章是否存在版权问题&#xff1f; 在当今科技飞速发展的时代&#xff0c;AI搜索工具如雨后春笋般涌现&#xff0c;为人们获取信息提供了极大的便利。然而&#xff0c;随之而来的问题是&#xff0c;AI搜索案例中常常出现很多内容缺乏依据&#xff0c;这引发…...

电脑关机的趣味小游戏——system函数、strcmp函数、goto语句的使用

文章目录 前言一. system函数1.1 system函数清理屏幕1.2 system函数暂停运行1.3 system函数电脑关机、重启 二、strcmp函数三、goto语句四、电脑关机小游戏4.1. 程序要求4.2. 游戏代码 总结 前言 今天我们写一点稍微有趣的代码&#xff0c;比如写一个小程序使电脑关机&#xf…...

AttributeError: ‘DataFrame‘ object has no attribute ‘append‘的参考解决方法

文章目录 写在前面一、问题描述二、解决方法参考链接 写在前面 自己的测试环境&#xff1a; Ubuntu20.04 一、问题描述 运行开源的python代码的时候&#xff0c;遇到如下问题 AttributeError: DataFrame object has no attribute append二、解决方法 报错中的DataFrame是在…...

java垃圾回收机制介绍

Java垃圾回收机制&#xff08;Garbage Collection, GC&#xff09;是Java编程语言中的一项重要特性&#xff0c;它自动管理内存&#xff0c;释放不再使用的对象 1. 堆&#xff08;Heap&#xff09;&#xff1a; • Java虚拟机&#xff08;JVM&#xff09;中用于存储对象实例的内…...

SpringMVC跨域问题解决方案

当Web应用程序尝试从一个源&#xff08;例如 http://localhost:9090&#xff09;向另一个不同的源&#xff08;例如 http://localhost:8080&#xff09;发起请求时&#xff0c;发现报错&#xff1a; 报错原因&#xff1a;请求被CORS策略拦截了 跨域问题概述 当Web应用程序尝试…...

【语音识别】Zipformer

Zipformer 是kaldi 团队于2024研发的序列建模模型。相比较于 Conformer、Squeezeformer、E-Branchformer等主流 ASR 模型&#xff0c;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模块&#xff08;一&#xff09;数据提取的精确性&#xff08;二&#xff09;处理复杂的文本结构&#xff08;三&#xff09;提高数据处理效率 二、正则表达式的内涵&#xff08;一&#xff09;、常用元字符&#xff08;二&#xff09;、量…...

为什么选择 React Native 作为跨端方案

为什么选择 React Native 作为跨端方案 我深刻地知道&#xff0c;没有完美的跨端技术&#xff0c;只有适合的场景。脱离适用场景去谈跨端技术没有什么意义。 适用场景 1. 业务更新迭代较快的团队与出海团队 React Native 特别适合那些业务更新频繁、需要快速响应市场的团队…...

服务器与普通电脑有什么区别?

服务器和普通电脑&#xff08;通常指的是个人计算机&#xff0c;即PC&#xff09;有众多相似之处&#xff0c;主要构成包含&#xff1a;CPU&#xff0c;内存&#xff0c;芯片&#xff0c;I/O总线设备&#xff0c;电源&#xff0c;机箱及操作系统软件等&#xff0c;鉴于使用要求…...

Oracle 12c Data Guard 环境中的 GAP 修复方法

概述 上文中提到Oracle 12c 引入了多项新技术来简化 Data Guard 环境中的 GAP 修复过程&#xff0c;如&#xff08;RECOVER … FROM SERVICE&#xff09;。这些新特性不仅减少了操作步骤&#xff0c;还提高了效率和准确性。本文档将详细说明如何利用这些新特性进行 GAP 修复。…...

力扣 三角dp

动态规划基础题&#xff0c;当前所在元素来自上一行的两列的值。 题目 从图可以看出&#xff0c;每一行的第一个数与最后一个数都是1&#xff0c;然后中间的数是来自它左上方和右上方的数的和。当然并不是要打印这个三角形的形状&#xff0c;因此可以想到正常的打印方式应该是…...

SQL基础语法全解析(上篇)

一、基本概念 1. 数据库术语 数据库&#xff08;database&#xff09; - 保存有组织的数据的容器&#xff08;通常是一个文件或一组文件&#xff09;。数据表&#xff08;table&#xff09; - 某种特定类型数据的结构化清单。模式&#xff08;schema&#xff09; - 关于数据库…...

【笔记】Linux服务器端使用百度网盘

1、在python环境下&#xff0c;下载bypy pip install bypy 2、第一次连接需要认证 bypy info 认证通过后百度网盘会出现bypy文件夹&#xff0c;如下 3、查看当前文件夹下的文件 bypy list 若有很多文件夹&#xff0c;可在后面增加文件夹名称&#xff0c;列出对应位置下的文件&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红队评估实战靶场(四)

靶机链接&#xff1a;http://vulnstack.qiyuanxuetang.net/vuln/detail/6/ 环境搭建 新建两张仅主机网卡&#xff0c;一张192.168.183.0网段&#xff08;内网网卡&#xff09;&#xff0c;一张192.168.157.0网段&#xff08;模拟外网网段&#xff09;&#xff0c;然后按照拓补…...

相机Camera日志实例分析之二:相机Camx【专业模式开启直方图拍照】单帧流程日志详解

【关注我&#xff0c;后续持续新增专题博文&#xff0c;谢谢&#xff01;&#xff01;&#xff01;】 上一篇我们讲了&#xff1a; 这一篇我们开始讲&#xff1a; 目录 一、场景操作步骤 二、日志基础关键字分级如下 三、场景日志如下&#xff1a; 一、场景操作步骤 操作步…...

NLP学习路线图(二十三):长短期记忆网络(LSTM)

在自然语言处理(NLP)领域,我们时刻面临着处理序列数据的核心挑战。无论是理解句子的结构、分析文本的情感,还是实现语言的翻译,都需要模型能够捕捉词语之间依时序产生的复杂依赖关系。传统的神经网络结构在处理这种序列依赖时显得力不从心,而循环神经网络(RNN) 曾被视为…...

【Go语言基础【13】】函数、闭包、方法

文章目录 零、概述一、函数基础1、函数基础概念2、参数传递机制3、返回值特性3.1. 多返回值3.2. 命名返回值3.3. 错误处理 二、函数类型与高阶函数1. 函数类型定义2. 高阶函数&#xff08;函数作为参数、返回值&#xff09; 三、匿名函数与闭包1. 匿名函数&#xff08;Lambda函…...

【Linux系统】Linux环境变量:系统配置的隐形指挥官

。# Linux系列 文章目录 前言一、环境变量的概念二、常见的环境变量三、环境变量特点及其相关指令3.1 环境变量的全局性3.2、环境变量的生命周期 四、环境变量的组织方式五、C语言对环境变量的操作5.1 设置环境变量&#xff1a;setenv5.2 删除环境变量:unsetenv5.3 遍历所有环境…...

如何应对敏捷转型中的团队阻力

应对敏捷转型中的团队阻力需要明确沟通敏捷转型目的、提升团队参与感、提供充分的培训与支持、逐步推进敏捷实践、建立清晰的奖励和反馈机制。其中&#xff0c;明确沟通敏捷转型目的尤为关键&#xff0c;团队成员只有清晰理解转型背后的原因和利益&#xff0c;才能降低对变化的…...

论文阅读:LLM4Drive: A Survey of Large Language Models for Autonomous Driving

地址&#xff1a;LLM4Drive: A Survey of Large Language Models for Autonomous Driving 摘要翻译 自动驾驶技术作为推动交通和城市出行变革的催化剂&#xff0c;正从基于规则的系统向数据驱动策略转变。传统的模块化系统受限于级联模块间的累积误差和缺乏灵活性的预设规则。…...

vue3 daterange正则踩坑

<el-form-item label"空置时间" prop"vacantTime"> <el-date-picker v-model"form.vacantTime" type"daterange" start-placeholder"开始日期" end-placeholder"结束日期" clearable :editable"fal…...

高考志愿填报管理系统---开发介绍

高考志愿填报管理系统是一款专为教育机构、学校和教师设计的学生信息管理和志愿填报辅助平台。系统基于Django框架开发&#xff0c;采用现代化的Web技术&#xff0c;为教育工作者提供高效、安全、便捷的学生管理解决方案。 ## &#x1f4cb; 系统概述 ### &#x1f3af; 系统定…...

热门Chrome扩展程序存在明文传输风险,用户隐私安全受威胁

赛门铁克威胁猎手团队最新报告披露&#xff0c;数款拥有数百万活跃用户的Chrome扩展程序正在通过未加密的HTTP连接静默泄露用户敏感数据&#xff0c;严重威胁用户隐私安全。 知名扩展程序存在明文传输风险 尽管宣称提供安全浏览、数据分析或便捷界面等功能&#xff0c;但SEMR…...

背包问题双雄:01 背包与完全背包详解(Java 实现)

一、背包问题概述 背包问题是动态规划领域的经典问题&#xff0c;其核心在于如何在有限容量的背包中选择物品&#xff0c;使得总价值最大化。根据物品选择规则的不同&#xff0c;主要分为两类&#xff1a; 01 背包&#xff1a;每件物品最多选 1 次&#xff08;选或不选&#…...