如何在C++中实现一个正确的时间循环器详解

前言

实际工程中可能会有这样一类普遍需求:在服务中,单独起一个线程,以一个固定的时间间隔,周期性地完成特定的任务。我们把这种问题抽象成一个时间循环器。

Naive Way

class TimerCircle {
 private:
 std::atomic_bool running_{false};
 uint64_t     sleep_{0UL};
 std::thread   thread_;

 public:
 explicit TimerCircle(uint64_t s) : sleep_{s} {}
 ~TimerCircle() {
  if (thread_.joinable()) {
   terminate();
   thread_.join();
  }
 }
 TimerCircle(const TimerCircle&) = delete;
 TimerCircle& operator=(const TimerCircle&) = delete;
 TimerCircle(TimerCircle&&) = default;
 TimerCircle& operator=(TimerCircle&&) = default;

 public:
 void launch() {
  thread_ = std::move(std::thread(&TimerCircle::loop, this));
 }
 void terminate() {
  running_.store(false);
 }
 void loop() {
  running_.store(true);
  while (running_.load()) {
   do_something();
   std::this_thread::sleep_for(std::chrono::seconds(sleep_));
  }
 }

 private:
 void do_something() const = 0;
};

实现简单平凡,一眼就能看出来没啥问题,于是也没啥好说的。

细节里的魔鬼

唯一的魔鬼藏在细节里。如果 TimerCircle 类型的对象发生析构,那么析构该对象的线程最多会被阻塞 sleep_ 秒。如果周期很长,比如长达 6 小时,那这显然是不可接受。

为此,我们需要借助标准库的条件变量 std::condition_variable 的 wait_for 函数的帮助。首先看其函数签名

template <typename Rep, typename Period, typename Predicate>
bool wait_for(std::unique_lock<std::mutex>& lock,
       const std::chrono::duration<Rep, Period>& rel_time,
       Predicate pred);

函数接受三个参数。lock 是一个 unique_lock,它必须为调用 wait_for 的线程所锁住;rel_time 是一个时间段,表示超时时间;pred 是一个谓词,它要么返回 true 要么返回 false。

一旦调用,函数会阻塞当前线程,直到两种情况返回:

  • 超时;此时函数返回 pred()。
  • 条件变量被通知,且谓词返回 true;此时函数返回 true。

于是我们可以实现一个 Countdown 类

#include <chrono>
#include <condition_variable>
#include <mutex>

class Countdown final {
 private:
 bool  running_ = true;
 mutable std::mutex       mutex_;
 mutable std::condition_variable cv_;

 public:
 Countdown() = default;
 ~Countdown() = default;
 Countdown(const Countdown&) = delete;
 Countdown& operator=(const Countdown&) = delete;
 Countdown(Countdown&&) = delete;
 Countdown& operator=(Countdown&&) = delete;

 public:
 void terminate() {
  {
   std::lock_guard<std::mutex> lock(mutex_);
   running_ = false;
  }
  cv_.notify_all();
 }

 template <typename Rep, typename Peroid>
 bool wait_for(std::chrono::duration<Rep, Peroid>&& duration) const {
  std::unique_lock<std::mutex> lock(mutex_);
  bool terminated = cv_.wait_for(lock, duration, [&]() { return !running_; });
  return !terminated;
 }
};

于是,TimerCircle 就变成

class TimerCircle {
 private:
 uint64_t  sleep_{0UL};
 Countdown  cv_;
 std::thread thread_;

 public:
 explicit TimerCircle(uint64_t s) : sleep_{s} {}
 ~TimerCircle() {
  if (thread_.joinable()) {
   terminate();
   thread_.join();
  }
 }
 TimerCircle(const TimerCircle&) = delete;
 TimerCircle& operator=(const TimerCircle&) = delete;
 TimerCircle(TimerCircle&&) = default;
 TimerCircle& operator=(TimerCircle&&) = default;

 public:
 void launch() {
  thread_ = std::move(std::thread(&TimerCircle::loop, this));
 }
 void terminate() {
  cv_.terminate();
 }
 void loop() {
  while (cv_.wait_for(std::chrono::seconds(sleep_))) {
   do_something();
  }
 }

 private:
 void do_something() const = 0;
};

简单,明了。

总结

到此这篇关于如何在C++中实现一个正确的时间循环器的文章就介绍到这了,更多相关C++实现时间循环器内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 如何在C++中实现一个正确的时间循环器详解

    前言 实际工程中可能会有这样一类普遍需求:在服务中,单独起一个线程,以一个固定的时间间隔,周期性地完成特定的任务.我们把这种问题抽象成一个时间循环器. Naive Way class TimerCircle { private: std::atomic_bool running_{false}; uint64_t sleep_{0UL}; std::thread thread_; public: explicit TimerCircle(uint64_t s) : sleep_{s} {} ~T

  • 如何在vue中更优雅的封装第三方组件详解

    目录 一.需求场景描述 二.关键技术点介绍 1.v-bind="$attrs" 2.v-on="$listeners" 三.封装el-image的代码示例 总结 一.需求场景描述 实际开发的时候,为了减少重复造轮子,提高工作效率,节省开发时间成本, 免不了会使用ui组件库,比如在web前端很受欢迎的element-ui. 但有的时候,我们需要在原组件的基础上做些改造,比如一个image组件, 我们需要统一在图片加载失败的时候展示的特定图,每次使用组件都加一遍, 麻烦

  • 在yii中新增一个用户验证的方法详解

    1.为什么要新增一个用户验证:因为我要将网站后台和前台做在同一个yii的应用中.但是前台也包含有会员的管理中心.而这两个用户验证是完全不同的,所以需要两个不同登陆页面,要将用户信息保存在不同的cookie或session中.所以需要在一个应用中增加一个用户验证2.yii的用户验证:在自定义用户验证前,我们首先要弄清楚yii的验证和授权方式.为了验证一个用户,我们需要定义一个有验证逻辑的验证类.在yii中这个类需要实现IUserIdentity接口,不同的类就可以实现不同的验证方 法.网站登陆一般

  • 如何在Django中添加没有微秒的 DateTimeField 属性详解

    前言 今天在项目中遇到一个Django的大坑,一个很简单的分页问题,造成了数据重复.最后排查发现是DateTimeField 属性引起的. 下面描述下问题,下面是我需要用到的一个 Task Model 基本定义: class Task(models.Model): # ...... 省略了其他字段 title = models.CharField(max_length=256, verbose_name=u'标题') created_at = models.DateTimeField(auto_

  • java中表示一个文件的File类型详解

    前言 从本篇文章开始,我们将开启对 Java IO 系统的学习,本质上就是对文件的读写操作,听上去简单,其实并不容易.Java 的 IO 系统一直在完善和改进,设计了大量的类,也只有理解了这些类型被设计出来的意义以及各自的应用场景,才能提升文件 IO 的理解. 那么,第一步就是要解决如何表示一个文件的问题,Java 世界中「万物皆对象」,如何将一个实际磁盘文件或目录对应到一个 Java 对象则是我们首要的问题. Java 中使用 File 来抽象一个文件,无论是普通文件或是目录,都可对应于一个

  • 如何在JavaScript中优雅的提取循环内数据详解

    前言 在本文中,我们将介绍两种提取循环内数据的方法:内部迭代和外部迭代.分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧 循环 举个例子,假设有一个函数 logFiles(): const fs = require('fs'); const path = require('path'); function logFiles(dir) { for (const fileName of fs.readdirSync(dir)) { // (A) const filePath = pat

  • 如何在Spring中使用编码方式动态配置Bean详解

    bean与spring容器的关系 Bean配置信息定义了Bean的实现及依赖关系,Spring容器根据各种形式的Bean配置信息在容器内部建立Bean定义注册表,然后根据注册表加载.实例化Bean,并建立Bean和Bean的依赖关系,最后将这些准备就绪的Bean放到Bean缓存池中,以供外层的应用程序进行调用. 本文将给大家详细介绍关于在Spring中使用编码方式动态配置Bean的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 1 DefaultListableBea

  • 如何在 C++ 中实现一个单例类模板

    单例模式是最简单的设计模式之一.在实际工程中,如果一个类的对象重复持有资源的成本很高,且对外接口是线程安全的,我们往往倾向于将其以单例模式管理. 此篇我们在 C++ 中实现正确的单例模式. 选型 在 C++ 中,单例模式有两种方案可选. 一是实现一个没有可用的公开构造函数的基类,并提供 GetInstance 之类的静态接口,以便访问子类唯一的对象.由于子类构造必须调用基类构造,但基类无公开构造函数可用,这使得子类对象只能由基类及基类的友元来构造,从而在机制上保证单例. 二是实现一个类模板,其模

  • 如何在Java中实现一个散列表

    目录 前言: 优化1 优化2 优化3 如何实现 总结 前言: 假设现在有一篇很长的文档,如果希望统计文档中每个单词在文档中出现了多少次,应该怎么做呢? 很简单! 我们可以建一个HashMap,以String类型为Key,Int类型为Value: 遍历文档中的每个单词 word ,找到键值对中key为 word 的项,并对相关的value进行自增操作. 如果该key= word 的项在 HashMap中不存在,我们就插入一个(word,1)的项表示新增. 这样每组键值对表示的就是某个单词对应的数量

  • 如何在CocosCreator中做一个List

    CocosCreator版本:2.3.4 cocos没有List组件,所以要自己写.从cocos的example项目中找到assets/case/02_ui/05_listView的demo来改造. 自写一个虚拟列表,有垂直布局,水平布局,网格布局和Padding的List Demo地址:https://files-cdn.cnblogs.com/files/gamedaybyday/cocos2.3.4_ListViewDemo_Grid.7z cocos原来的LayOut做列表,有100个数

随机推荐