C++笔试题之实现一个定时器
一.定时器(timer)的需求
1.执行定时任务的时,主线程不阻塞,所以timer必须至少持有一个线程用于执行定时任务
2.考虑到timer线程资源的合理利用,一个timer需要能够管理多个定时任务,所以timer要支持增删任务,通过容器储存任务
3.当timer空闲时(即没有任务或执行任务的时刻未到),timer中的线程不应该空转来占用资源,可通过条件变量实现
4.支持重复任务和非重复任务
二.定时器(timer)的实现
#include <algorithm>
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <functional>
#include <map>
#include <mutex>
#include <thread>
#include <iostream>
#include <iomanip>
#include <sstream>namespace CC
{
using TaskFunc = std::function<void()>;struct Task
{uint64_t id;uint64_t period;bool repeated;TaskFunc func;bool removed;Task(uint64_t id, uint64_t period, bool repeated, TaskFunc func): id(id), period(period), repeated(repeated), func(func), removed(false){}
};class Timer
{
public:Timer() : m_stop(false){m_worker = std::thread(&Timer::run, this);}~Timer(){m_stop.store(true);m_condition.notify_all();m_worker.join();}uint64_t add(uint64_t period_ms, bool repeated, TaskFunc func){uint64_t when = now() + period_ms;Task task(m_cur_id, period_ms, repeated, func);{std::lock_guard<std::mutex> lock(m_tasks_mutex);m_tasks.insert({when, task});}m_condition.notify_all();return m_cur_id++;}// Timer::remove并没有真正的将定时任务删除,仅仅是将removed标志位设置为true,删除操作实际是在Timer::run中进行的。// 为什么要这么做?如果在这里如果由Timer::remove来执行m_tasks.erase(it),那么有可能删除的是Timer::run里正在执行的那个任务,这是明显不对的。// 所以才采用将removed标志位设置为true的这种做法。bool remove(uint64_t id){bool flag = false;std::lock_guard<std::mutex> lock(m_tasks_mutex);std::multimap<uint64_t, Task>::iterator it =std::find_if(m_tasks.begin(), m_tasks.end(),[id](const std::pair<uint64_t, Task> &item) -> bool { return item.second.id == id; });if (it != m_tasks.end()){it->second.removed = true;flag = true;}return flag;}private:std::thread m_worker;std::atomic<bool> m_stop;std::multimap<uint64_t, Task> m_tasks;std::mutex m_tasks_mutex;std::condition_variable m_condition;uint64_t m_cur_id;// m_condition.wait之后继续向下执行,此时如果m_stop是true,那么表明timer要被停止了,那线程也要结束,所以一个break跳出最开始的while (true)循环,让线程执行结束。// 如果m_stop是false,那表明现有可能有定时任务需要执行了。取出第一个任务m_tasks.begin(),也是按时间排序最靠前的任务。用任务的时刻和当前时刻对比:// 如果“时辰已到”,那就执行。执行的之后需要注意的是,要将锁释放lock.unlock(),因为继续持有没有任何意义,反而会阻塞住对m_tasks的一些操作。// 如果“时辰未到”,那就执行m_condition.wait_for,让当前线程休眠,直到std::chrono::milliseconds(task_time - cur_time)这段时间过去或者被唤醒。void run(){while (true){std::unique_lock<std::mutex> lock(m_tasks_mutex);m_condition.wait(lock, [this]() -> bool { return !m_tasks.empty() || m_stop; });if (m_stop){break;}uint64_t cur_time = now();std::multimap<uint64_t, Task>::iterator it = m_tasks.begin();uint64_t task_time = it->first;if (cur_time >= task_time){Task &cur_task = it->second;if (!cur_task.removed){lock.unlock();cur_task.func();lock.lock();if (cur_task.repeated && !cur_task.removed){uint64_t when = cur_time + cur_task.period;Task new_task(cur_task.id, cur_task.period, cur_task.repeated, cur_task.func);m_tasks.insert({when, new_task});}}m_tasks.erase(it);}else{m_condition.wait_for(lock, std::chrono::milliseconds(task_time - cur_time));}}}uint64_t now() //ms{auto now = std::chrono::system_clock::now();auto duration = now.time_since_epoch();return std::chrono::duration_cast<std::chrono::milliseconds>(duration).count();}
};} // namespace CC// 格式化时间,精确到毫秒.
std::string getTimeString()
{auto now = std::chrono::system_clock::now();auto duration = now.time_since_epoch();auto millis = std::chrono::duration_cast<std::chrono::milliseconds>(duration).count();std::time_t time = std::chrono::system_clock::to_time_t(now);std::tm *tm = std::localtime(&time);std::stringstream ss;ss << std::put_time(tm, "%Y-%m-%d %H:%M:%S") << "." << std::setw(3) << std::setfill('0') << millis % 1000;return ss.str();
}// 待执行的任务
void theTask(int id)
{std::cout << getTimeString() << " id = " << id << std::endl;
}int main()
{CC::Timer *timer = new CC::Timer();timer->add(3000, false, std::bind(theTask, 1));uint64_t id = timer->add(2000, true, std::bind(theTask, 2));timer->add(1000, true, std::bind(theTask, 3));std::this_thread::sleep_for(std::chrono::seconds(3));timer->remove(id);std::this_thread::sleep_for(std::chrono::seconds(1));delete timer;std::this_thread::sleep_for(std::chrono::seconds(1));return 0;
}
参考链接:https://zhuanlan.zhihu.com/p/668916073
相关文章:

C++笔试题之实现一个定时器
一.定时器(timer)的需求 1.执行定时任务的时,主线程不阻塞,所以timer必须至少持有一个线程用于执行定时任务 2.考虑到timer线程资源的合理利用,一个timer需要能够管理多个定时任务,所以timer要支持增删任务…...

【英特尔IA-32架构软件开发者开发手册第3卷:系统编程指南】2001年版翻译,2-13
文件下载与邀请翻译者 学习英特尔开发手册,最好手里这个手册文件。原版是PDF文件。点击下方链接了解下载方法。 讲解下载英特尔开发手册的文章 翻译英特尔开发手册,会是一件耗时费力的工作。如果有愿意和我一起来做这件事的,那么ÿ…...

快消零售行业的培训创新:构建在线培训知识库
在快速消费品(FMCG)行业中,员工的培训和发展对于保持竞争力至关重要。随着电子商务的兴起和消费者行为的变化,快消零售行业需要不断适应新的市场趋势。在线培训知识库作为一种有效的培训工具,可以帮助企业提升员工技能…...

【AI开源项目】Botpress - 开源智能聊天机器人平台及其部署方案
文章目录 Botpress 概述Botpress 的定位 Botpress 的主要特点1. OpenAI 集成2. 易于使用3. 定制和扩展性4. 多平台支持5. 集成和扩展 API6. 活跃的社区和详尽的文档 部署方案集成集成开发集成部署机器人示例开发工具代理本地开发先决条件从源代码构建 Botpress 如何解决常见问题…...

一文读懂系列:SSL加密流量检测技术详解
SSL加密流量检测功能的主要目的是为了对加密流量做解密处理,并对解密后的流量做内容安全检查(比如反病毒、入侵防御、URL远程查询、内容过滤、文件过滤和邮件过滤等)和审计(防止信息泄露)。接下来我们详细介绍SSL加密流…...

Android Studio各种历史版本
下载地址:AndroidDevTools - Android开发工具 Android SDK下载 Android Studio下载 Gradle下载 SDK Tools下载...

大数据导论及分布式存储HadoopHDFS入门
思维导图 数据导论 数据是什么? 进入21世纪,我们的生活就迈入了"数据时代" 作为21世纪的新青年,"数据"一词经常出现。 数据无时无刻的在影响着我们的现实生活 什么是数据? 数据又如何影响现实生活? 数据…...

语言模型的采样方法
语言模型的采样方法 语言模型的输出为一个向量,该向量的每一维代表着词典中对应词的概率。 在采用自回归范式的文本生成任务中,语言模型将依次生成一组向量并将其解码为文本。将这组向量解码为文本的过程被成为语言模型解码。 解码过程显著影响着生成文本…...

使用 Nginx 配置真实 IP 地址转发
使用 Nginx 配置真实 IP 地址转发 在许多 web 应用程序中,获取客户端的真实 IP 地址非常重要,尤其是在使用反向代理服务器(如 Nginx)时。本文将指导你如何在 Nginx 中配置 X-Real-IP 和 X-Forwarded-For 头部,以确保你…...

WPF+MVVM案例实战与特效(二十四)- 粒子字体效果实现
文章目录 1、案例效果2、案例实现1、文件创建2.代码实现3、界面与功能代码3、总结1、案例效果 提示:这里可以添加本文要记录的大概内容: 2、案例实现 1、文件创建 打开 Wpf_Examples 项目,在 Views 文件夹下创建窗体界面 ParticleWindow.xaml,在 Models 文件夹下创建粒子…...

Oracle视频基础1.4.3练习
15个视频 1.4.3 できない dbca删除数据库 id ls cd cd dbs ls ls -l dbca# delete a database 勾选 # chris 勾选手动删除数据库 ls ls -l ls -l cd /u01/oradata ls cd /u01/admin/ ls cd chris/ ls clear 初始化参数文件,admin,数据文件#新版本了…...

energy 发布 v2.4.5
更新内容 修复 energy cli install 命令安装开发环境 修复 动态库加载error未暴露 增加 JS ipc.on 监听模式,异步返回结果 修复 energy cli 不能强制退出问题 修复 MacOS 开发模式 debug 时不更新 helper 进程 优化 energy cli 在 MacOS 开发模式和安装包制作 link…...

一文详解工单管理系统,工单系统是什么意思
在现代企业管理中,工单管理系统已经成为提升效率和客户满意度的重要工具。随着企业规模的扩大和业务复杂性的增加,传统的手工工单处理方式已经无法满足企业的需求。本文将详细解析工单管理系统的定义、功能、优势,并推荐一款优秀的工单管理系…...

【无标题】基于SpringBoot的母婴商城的设计与实现
一、项目背景 当前社会各行业领域竞争压力非常大,随着当前时代的信息化,科学化发展,让社会各行业领域都争相使用新的信息技术,对行业内的各种相关数据进行科学化,规范化管理。这样的大环境让那些止步不前,…...

你需要了解的Android主题相关知识
在 Android 开发中,主题(Theme)是用于定义应用的视觉风格的一组样式集合。主题决定了应用的配色、字体样式、控件外观等,是整个应用的一致性视觉体验的重要组成部分。以下是对 Android 主题的全面介绍,包括主题的基础概…...

基于Multisim数控直流稳压电源电路(含仿真和报告)
【全套资料.zip】数控直流稳压电源电路设计Multisim仿真设计数字电子技术 文章目录 功能一、Multisim仿真源文件二、原理文档报告资料下载【Multisim仿真报告讲解视频.zip】 功能 1.输出直流电压调节范围5-12V。 2.输出电流0-500mA。 3.输出直流电压能步进调节,步…...

精读预告Bigtable
文章目录 1. 引言:2. 背景 1. 引言: 在本期的精读会中,我们将深入解读另一篇具有里程碑意义的论文——《Bigtable: A Distributed Storage System for Structured Data》。这篇论文详细介绍了 Bigtable 作为谷歌用于管理结构化数据的分布式存…...

软件架构演变:从单体架构到LLM链式调用
0 前言 软件架构——我们数字世界的蓝图——自20世纪中叶计算机时代诞生以来,已经发生了巨大演变。 20世纪60年代和70年代早期,以大型主机和单体软件为主导。而今天,数字领域已完全不同,运行在由云计算、API连接、AI算法、微服务…...

Redis-“自动分片、一定程度的高可用性”(sharding水平拆分、failover故障转移)特性(Sentinel、Cluster)
文章目录 零、写在前面一、水平拆分(sharding/分片)、故障转移(failover)机制介绍水平拆分(Sharding)故障转移机制 二、Redis的水平拆分的机制有关的配置1. 环境准备2. 配置文件配置3. 启动所有Redis实例4. 创建集群5. 测试集群读/写6. 集群管理 三、Red…...

操作系统(9) (并发-----原子性/互斥临界区/生产者消费者问题/临界区问题三条件/互斥性/进展性/公平性)
目录 1. 并发(Concurrency)的定义 2. 原子性(Atomicity) 3. 互斥(Mutual Exclusion) 4. 生产者-消费者问题(Producer-Consumer Problem) 5. 临界区Critical Section 6. 临界区问题…...

Django响应
HTTPResponse: 是由Django创造的, 他的返回格式为 HTTPResponse(content响应体,content_type响应体数据类型,status状态码), 可以修改返回的数据类型,适用于返回图片,视频,音频等二进…...

算法:图的相关算法
图的相关算法 1. 图的遍历算法1.1 深度优先搜索1.2 广度优先搜索 2. 最小生成树求解算法普里姆(Prim)算法克鲁斯卡尔(Kruskal)算法 3. 拓扑排序4. 最短路径算法 1. 图的遍历算法 图的遍历是指从某个顶点出发,沿着某条搜索路径对图中的所有顶点进行访问且只访问次的…...

django的models使用介绍。
from django.db import modelsfrom utils.models import CommonModel# Create your models here. class User(CommonModel):#用户数据模型username models.CharField(用户名,max_length32, uniqueTrue)password models.CharField(密码,max_length256)nickname models.CharFi…...

【分布式技术】分布式事务深入理解
文章目录 概述产生原因关键点 分布式事务解决方案3PC3PC的三个阶段:3PC相比于2PC的改进:3PC的缺点: TCCTCC事务的三个阶段:TCC事务的设计原则:TCC事务的适用场景:TCC事务的优缺点:如何解决TCC模…...

力扣hot100-->hash表/map
hash表/map 1. 1. 两数之和 简单 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。 你可以假设每种输入只会对应一个答案,并且你不能使用两次相同的元素。 …...

基于redis实现延迟队列
Redis实现延时队列 延时队列里装的主要是延时任务,用延时队列来维护延时任务的执行时间。 1、延时队列有哪些使用情景? 1、如果请求加锁没加成功 可以将这个请求扔到延时队列里,延后处理。 2、业务中有延时任务的需要 比如说࿰…...

PHP微信小程序共享充电桩系统设计与实现计算机毕业设计源代码作品和开题报告
博主介绍:黄菊华老师《Vue.js入门与商城开发实战》《微信小程序商城开发》图书作者,CSDN博客专家,在线教育专家,CSDN钻石讲师;专注大学生毕业设计教育、辅导。 所有项目都配有从入门到精通的基础知识视频课程ÿ…...

【网络面试篇】TCP与UDP类
目录 一、综述 1. TCP与UDP的概念 2. 特点 3. 区别 4. 对应的使用场景 二、补充 1. 基础概念 (1)面向连接 (2)可靠的 (3)字节流 2. 相关问题 (1)TCP 和 UDP 可以同时绑定…...

Windows转Mac过渡指南
最近由于工作原因开始使用mac电脑,说实话刚拿到手的时候,window党表示真的用不惯。坚持用一下午之后,发现真的yyds,这篇文章说说mac电脑的基本入门指南。 1. 不会使用mac的触摸板,接上鼠标发现滚轮和windows是反的。 …...

LeetCode100之盛最多水的容器(11)--Java
1.问题描述 给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。返回容器可以储存的最大水量 注意 你不能倾斜容器 示例1 输入&…...