C++笔试题之实现一个定时器

news2024/11/26 14:35:33

一.定时器(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

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

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

相关文章

DICOM标准:CR图像模块属性详解——计算放射线照相术(CR)及其在DICOM中的表示

目录 CR图像及其在DICOM中的表示 1 计算放射线照相术 1.1 CR序列组件 1.1 -- CR 序列模块属性 1.2 CR 图像模块 表1.2 -- CR 图像模块属性 结论 CR图像及其在DICOM中的表示 计算放射线照相术&#xff08;Computed Radiography, CR&#xff09;是一种利用计算机技术对传统…

[Prometheus学习笔记]从架构到案例,一站式教程

文章目录 Prometheus 优势Prometheus 的组件、架构Prometheus Server 直接从监控目标中或者间接通过推送网关来拉取监控指标&#xff0c;它在本地存储所有抓取到的样本数据&#xff0c;并对此数据执行一系列规则&#xff0c;以汇总和记录现有数据的新时间序列或生成告警。可以通…

Javaweb梳理3——SQL概述+DDL语句1

Javaweb梳理3——SQL概述DDL语句1 Javaweb梳理3——SQL概述DDL语句13.1 SQL简介3.2 通用语法3.3 SQL分类3.4 DDL:操作数据库3.4.1 查询数据库3.4.2 创建数据库3.4.3 删除数据库3.4.4 使用数据库 Javaweb梳理3——SQL概述DDL语句1 3.1 SQL简介 英文&#xff1a;Structured Que…

HarmonyOS Next星河版笔记--界面开发(2)

ArkUI-界面开发 位置&#xff1a;在build(){}中去编写代码 //以前学基础 ->写代码的位置&#xff08;页面顶部&#xff09; Entry Component struct Index {State message: string Hello World; //构建 -> 页面build() {//行//列RelativeContainer() {//文本 函数名&a…

使用PostgreSQL进行高效数据管理

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 使用PostgreSQL进行高效数据管理 PostgreSQL简介 安装PostgreSQL 在Ubuntu上安装PostgreSQL 在CentOS上安装PostgreSQL 在macOS上…

vue3学习记录-单文件组件 CSS 功能

vue3学习记录-单文件组件 CSS 功能 1.组件作用域 CSS1.1为什么要用到样式穿透&#xff08;&#xff1a;deep&#xff08;&#xff09;&#xff09;1.2 插槽选择器:slotted(div)1.3 全局选择器:global 2.CSS Modules2.1 基本用法2.2 自定义注入名称2.3 与组合式 API 一同使用 3.…

3.1 快速启动Flink集群

文章目录 1. 环境配置2. 本地启动3. 集群启动4. 向集群提交作业4.1 提交作业概述4.2 添加打包插件4.3 将项目打包4.4 在Web UI上提交作业4.5 命令行提交作业 在本实战中&#xff0c;我们将快速启动Apache Flink 1.13.0集群&#xff0c;并在Hadoop集群环境中提交作业。首先&…

贪心算法---java---黑马

贪心算法 1)Greedy algorithm 称之为贪心算法或者贪婪算法&#xff0c;核心思想是 将寻找最优解的问题分为若干个步骤每一步骤都采用贪心原则&#xff0c;选取当前最优解因为未考虑所有可能&#xff0c;局部最优的堆叠不一定得到最终解最优 贪心算法例子 Dijkstra while …

产品宣传册如何分享到微信朋友圈

在这个互联网高速发展的时代&#xff0c;微信已经成为我们日常生活中不可或缺的社交工具。作为商家或个人&#xff0c;如何将产品宣传册分享到微信朋友圈&#xff0c;提高产品知名度和影响力&#xff0c;成为了一项至关重要的技能。 那要怎么操作呢&#xff1f; 1. 实用工具&a…

轻松入门WordPress:在Ubuntu上搭建本地网站并配置公网访问地址

文章目录 前言1. 安装WordPress2. 创建WordPress数据库3. 安装相对URL插件4. 安装内网穿透发布网站4.1 命令行方式&#xff1a;4.2. 配置wordpress公网地址 5. 配置WordPress固定公网地址 前言 本文主要介绍如何在Linux Ubuntu系统上使用WordPress搭建一个本地网站&#xff0c…

ubuntu编译内核安装启动

下载源码 apt update apt install linux-source # /usr/src/linux-source-5.4.0/linux-source-5.4.0.tar.bz2 下载源码 tar -jxvf linux-source-5.4.0.tar.bz2 # /usr/src/linux-source-5.4.0 解压源码 安装依赖 sudo apt -y install build-essential sudo apt -y i…

多个锚点定位时的锚点优选方法(附公式和MATLAB代码讲解)

在多锚点定位系统中,锚点的选择对定位精度有重要影响。以下是几种常见的锚点选优方法,配合相应的公式和MATLAB代码示例进行详细分析。 文章目录 基于几何分布的选择基于距离最小化的选择加权优化选择总结基于几何分布的选择 方法描述: 锚点的几何分布影响定位的可辨识性。选…

DICOM标准:US超声模块属性详解——超声医学的DICOM标准解析

引言 数字成像和通信在医学领域中的应用极为广泛&#xff0c;其中DICOM&#xff08;数字成像和通信医学&#xff09;标准对于确保不同设备和系统之间的兼容性和互操作性至关重要。本文将详细介绍DICOM标准中关于超声医学&#xff08;Ultrasound, US&#xff09;的部分&#xff…

华为鲲鹏一体机 安装笔记

安装驱动 在这个链接 社区版-固件与驱动-昇腾社区 1 下载NPU固件 需要注册登录&#xff0c;否则报错&#xff1a; ERR_NO:0x0091;ERR_DES:HwHiAiUser not exists! Please add HwHi AiUser 准备软件包-软件安装-CANN…

STM32H750 USBCDC配置与使用

STM32H750 USBCDC配置与使用 &#x1f4cd;相关参考文章&#xff1a;《STM32 USB CDC VPC》 STM32H750VB有2个USB OTG接口&#xff08;1FS&#xff0c;1HS/FS&#xff09;无晶振型解决方案&#xff0c;带有LPM和BCD。 &#x1f516;本次使用USB-PTG-FS作为测试 &#x1f33f;…

引领数字时代:万码优才如何变革IT人才招聘新体验(这里有更精准的推荐)

目录 引领数字时代&#xff1a;万码优才如何变革IT人才招聘新体验引领未来科技&#xff0c;精准链接IT精英精准匹配&#xff0c;高效对接海量资源&#xff0c;覆盖广泛优化体验&#xff0c;简化流程 全面升级&#xff1a;AI赋能数字人才职业成长AI模拟面试职场千问智能简历评估…

网络安全法详细介绍——爬虫教程

目录 [TOC](目录)一、网络安全法详细介绍1. 网络安全法的主要条款与作用2. 网络安全法与爬虫的关系3. 合法使用爬虫的指南 二、爬虫的详细教程1. 准备环境与安装工具2. 使用requests库发送请求3. 解析HTML内容4. 使用robots.txt规范爬虫行为5. 设置请求间隔6. 数据清洗与存储 三…

vscode插件-08 Golang

文章目录 Go安装其他必须软件 Go Go语言环境&#xff0c;只需安装这一个插件。然后通过vscode命令下载安装其他go环境需要的内容。 程序调试&#xff0c;需要创建.vscode文件夹并编写launch.json文件。 安装其他必须软件 ctrlshiftp&#xff0c;调出命令面板&#xff0c;输入…

ConnectX-7 25/50/100/200/400G NIC

ConnectX-7 25/50/100/200/400G NIC ConnectX-7提供了广泛的软件定义、硬件加速的网络、存储和安全功能&#xff0c;使组织能够现代化和保护他们的IT基础设施。此外&#xff0c;ConnectX-7还支持从边缘到核心数据中心到云的敏捷和高性能解决方案&#xff0c;同时增强网络安全性…

windows在两台机器上测试 MySQL 集群实现实时备份

在两台机器上测试 MySQL 集群实现实时备份的基本步骤&#xff1a; 一、环境准备 机器配置 确保两台机器&#xff08;假设为服务器 A 和服务器 B&#xff09;能够互相通信&#xff0c;例如它们在同一个局域网内&#xff0c;并且开放了 MySQL 通信所需的端口&#xff08;默认是 …