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

 更新时间:2020年10月15日 09:25:07   作者:始终  
这篇文章主要给大家介绍了关于如何在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++实现时间循环器内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • c++标准输入输出流关系的前世今生

    c++标准输入输出流关系的前世今生

    这篇文章主要给大家介绍了关于c++标准输入输出流关系的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-04-04
  • C++实现希尔排序(ShellSort)

    C++实现希尔排序(ShellSort)

    这篇文章主要为大家详细介绍了C++实现希尔排序,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-04-04
  • 详解C语言求两个数的最大公约数及最小公倍数的方法

    详解C语言求两个数的最大公约数及最小公倍数的方法

    这篇文章主要介绍了C语言求两个数的最大公约数及最小公倍数的方法,辗转相除法和辗转相减法在解决这种问题时最常用到,需要的朋友可以参考下
    2016-03-03
  • C语言算法的时间复杂度和空间复杂度

    C语言算法的时间复杂度和空间复杂度

    这篇文章主要介绍了C语言算法的时间复杂度和空间复杂度,算法在编写成可执行程序后,运行时需要耗费时间资源和空间(内存)资源,更多相关需要的朋友可以参考一下
    2022-07-07
  • C/C++ break和continue区别及使用方法

    C/C++ break和continue区别及使用方法

    这篇文章主要介绍了C/C++ break和continue区别及使用方法的相关资料,需要的朋友可以参考下
    2017-07-07
  • c++函数中的指针参数与地址参数区别介绍

    c++函数中的指针参数与地址参数区别介绍

    c++函数中的指针参数与地址参数区别介绍;可供参考
    2012-11-11
  • 获取C语言中int类型的最大值的方法小结

    获取C语言中int类型的最大值的方法小结

    在C语言中,int 类型的大小通常是根据系统架构来决定的,在大多数现代系统上,int 通常是32位的,在C语言中,获取int类型的最大值有几种不同的方法,下面,我们将讨论两种方法:使用标准库函数和使用算法,需要的朋友可以参考下
    2024-06-06
  • 基于C语言实现计算生辰八字五行的示例详解

    基于C语言实现计算生辰八字五行的示例详解

    生辰八字,简称八字,是指一个人出生时的干支历日期;年月日时共四柱干支,每柱两字,合共八个字。这篇文章主要介绍了C语言实现计算生辰八字五行的示例代码,需要的可以参考一下
    2023-03-03
  • 浅谈C++中对象的复制与对象之间的相互赋值

    浅谈C++中对象的复制与对象之间的相互赋值

    这篇文章主要介绍了浅谈C++中对象的复制与对象之间的相互赋值,是C语言入门学习中的基础知识,需要的朋友可以参考下
    2015-09-09
  • C语言绘制雷达图的示例代码

    C语言绘制雷达图的示例代码

    常用的统计图有条形图、柱形图、折线图、曲线图、饼图、环形图、扇形图,其中还有一种雷达图的绘制也较难,本文为大家提供了雷达图的绘制方法,需要的可以参考下
    2024-02-02

最新评论