ToB企服应用市场:ToB评测及商务社交产业平台

标题: C++编程:实现一个跨平台安全的定时器Timer模块 [打印本页]

作者: 前进之路    时间: 2024-7-24 08:18
标题: C++编程:实现一个跨平台安全的定时器Timer模块
0. 概要

对于C++应用编程,定时器模块是一个至关重要的组件。为了确保系统的可靠性和功能安全,我们需要设计一个高效、稳固的定时器。
本文将实现一个跨平台安全的C++ SafeTimer 定时器模块,并提供完备的gtest单元测试。
完备代码见 gitee_safe_timer
雷同设计请参阅文章:C++编程: 线程池封装、使命异步执行以及使命延迟执行
1. 设计目标

目标是创建一个符合功能安全要求的定时器模块,具体包罗以下几点:
2. SafeTimer 类的实现

SafeTimer 类是我们实现的焦点,它提供了单次触发(SingleShot)和重复触发(Repeat)两种定时功能,同时还支持暂停(Pause)和恢复(Resume)。以下是 SafeTimer 类的完备实现。
2.1 头文件 safe_timer.h

  1. #ifndef SAFE_TIMER_H
  2. #define SAFE_TIMER_H
  3. #include <atomic>
  4. #include <chrono>
  5. #include <condition_variable>
  6. #include <functional>
  7. #include <memory>
  8. #include <mutex>
  9. #include <string>
  10. #include <thread>
  11. // 定义SafeTimer类,用于管理定时任务
  12. class SafeTimer {
  13. public:
  14.   // 构造函数,可以指定定时器的名称,默认为"SafeTimer"
  15.   explicit SafeTimer(const std::string& name = "SafeTimer") noexcept;
  16.   // 析构函数
  17.   virtual ~SafeTimer() noexcept;
  18.   // 禁止复制构造和赋值操作
  19.   SafeTimer(const SafeTimer&) = delete;
  20.   SafeTimer& operator=(const SafeTimer&) = delete;
  21.   // 返回定时器的名称
  22.   std::string GetName() const noexcept;
  23.   // 返回定时器是否处于循环模式
  24.   bool IsLoop() const noexcept;
  25.   // 设置一个一次性定时任务
  26.   template <typename Callable, typename... Arguments>
  27.   bool SingleShot(uint64_t interval_in_millis, Callable&& func, Arguments&&... args);
  28.   // 设置一个可重复的定时任务
  29.   template <typename Callable, typename... Arguments>
  30.   bool Repeat(uint64_t interval_in_millis, Callable&& func, Arguments&&... args);
  31.   // 设置一个可重复的定时任务,可以选择是否立即执行一次
  32.   template <typename Callable, typename... Arguments>
  33.   bool Repeat(uint64_t interval_in_millis, bool call_func_immediately, Callable&& func, Arguments&&... args);
  34.   // 取消当前的定时任务
  35.   void Cancel() noexcept;
  36.   // 暂停当前的定时任务
  37.   bool Pause() noexcept;
  38.   // 恢复已暂停的定时任务
  39.   void Resume() noexcept;
  40.   // 判断定时器是否处于空闲状态
  41.   bool IsTimerIdle() const noexcept;
  42. private:
  43.   // 启动定时任务的核心函数
  44.   bool Start(uint64_t interval_in_millis, std::function<void()> callback, bool loop, bool callback_immediately = false);
  45.   // 尝试使定时器过期,用于取消或暂停任务
  46.   void TryExpire() noexcept;
  47.   // 销毁线程资源
  48.   void DestroyThread() noexcept;
  49. private:
  50.   // 定时器的名称
  51.   std::string name_;
  52.   // 标记定时器是否为循环模式
  53.   bool is_loop_;
  54.   // 原子布尔类型,标记定时器是否已经过期
  55.   std::atomic_bool is_expired_;
  56.   // 原子布尔类型,标记是否尝试使定时器过期
  57.   std::atomic_bool try_to_expire_;
  58.   // 独占所有权的线程智能指针
  59.   std::unique_ptr<std::thread> thread_;
  60.   // 互斥锁,用于线程同步
  61.   std::mutex mutex_;
  62.   // 条件变量,用于线程间的通信
  63.   std::condition_variable condition_;
  64.   // 定时器启动时的时间点
  65.   std::chrono::time_point<std::chrono::steady_clock> start_time_;
  66.   // 定时器结束时的时间点
  67.   std::chrono::time_point<std::chrono::steady_clock> end_time_;
  68.   // 剩余任务时间(毫秒)
  69.   uint64_t task_remain_time_ms_;
  70.   // 回调函数,当定时器过期时调用
  71.   std::function<void()> callback_;
  72. };
  73. // 实现模板成员函数
  74. // 单次定时任务的实现
  75. template <typename Callable, typename... Arguments>
  76. bool SafeTimer::SingleShot(uint64_t interval_in_millis, Callable&& func, Arguments&&... args) {
  77.   // 创建一个绑定的函数对象,用于延迟执行
  78.   auto action = std::bind(std::forward<Callable>(func), std::forward<Arguments>(args)...);
  79.   // 调用私有的Start函数,设置一次性任务
  80.   return Start(interval_in_millis, action, false);
  81. }
  82. // 循环定时任务的实现
  83. template <typename Callable, typename... Arguments>
  84. bool SafeTimer::Repeat(uint64_t interval_in_millis, Callable&& func, Arguments&&... args) {
  85.   // 创建一个绑定的函数对象,用于延迟执行
  86.   auto action = std::bind(std::forward<Callable>(func), std::forward<Arguments>(args)...);
  87.   // 调用私有的Start函数,设置循环任务
  88.   return Start(interval_in_millis, action, true);
  89. }
  90. // 循环定时任务的实现,允许指定是否立即执行一次
  91. template <typename Callable, typename... Arguments>
  92. bool SafeTimer::Repeat(uint64_t interval_in_millis, bool call_func_immediately, Callable&& func, Arguments&&... args) {
  93.   // 创建一个绑定的函数对象,用于延迟执行
  94.   auto action = std::bind(std::forward<Callable>(func), std::forward<Arguments>(args)...);
  95.   // 调用私有的Start函数,设置循环任务,可选择立即执行
  96.   return Start(interval_in_millis, action, true, call_func_immediately);
  97. }
  98. #endif  // SAFE_TIMER_H
复制代码
源文件 safe_timer.cpp

  1. #include "safe_timer.h"
  2. #include <iostream>
  3. SafeTimer::SafeTimer(const std::string& name) noexcept
  4.     : name_(name), is_loop_(false), is_expired_(true), try_to_expire_(false), task_remain_time_ms_(0), callback_(nullptr) {}
  5. SafeTimer::~SafeTimer() noexcept {
  6.   TryExpire();
  7. }
  8. std::string SafeTimer::GetName() const noexcept {
  9.   return name_;
  10. }
  11. bool SafeTimer::IsLoop() const noexcept {
  12.   return is_loop_;
  13. }
  14. void SafeTimer::Cancel() noexcept {
  15.   if (is_expired_ || try_to_expire_ || !thread_) {
  16.     return;
  17.   }
  18.   TryExpire();
  19. }
  20. bool SafeTimer::Pause() noexcept {
  21.   if (is_expired_) {
  22.     return false;
  23.   }
  24.   auto now = std::chrono::steady_clock::now();
  25.   auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - start_time_).count();
  26.   auto remaining = std::chrono::duration_cast<std::chrono::milliseconds>(end_time_ - now).count();
  27.   if (remaining <= 0) {
  28.     return false;
  29.   }
  30.   Cancel();
  31.   task_remain_time_ms_ = static_cast<uint64_t>(remaining);
  32.   return true;
  33. }
  34. void SafeTimer::Resume() noexcept {
  35.   if (task_remain_time_ms_ > 0 && callback_) {
  36.     Start(task_remain_time_ms_, callback_, false, false);
  37.     task_remain_time_ms_ = 0;
  38.   }
  39. }
  40. bool SafeTimer::IsTimerIdle() const noexcept {
  41.   return is_expired_ && !try_to_expire_;
  42. }
  43. bool SafeTimer::Start(uint64_t interval_in_millis, std::function<void()> callback, bool loop, bool callback_immediately) {
  44.   if (!is_expired_ || try_to_expire_) {
  45.     return false;
  46.   }
  47.   is_expired_ = false;
  48.   is_loop_ = loop;
  49.   DestroyThread();
  50.   thread_ = std::make_unique<std::thread>([this, interval_in_millis, callback, callback_immediately]() {
  51.     if (callback_immediately) {
  52.       callback();
  53.     }
  54.     while (!try_to_expire_) {
  55.       callback_ = callback;
  56.       start_time_ = std::chrono::steady_clock::now();
  57.       end_time_ = start_time_ + std::chrono::milliseconds(interval_in_millis);
  58.       std::unique_lock<std::mutex> lock(mutex_);
  59.       condition_.wait_until(lock, end_time_);
  60.       if (try_to_expire_) {
  61.         break;
  62.       }
  63.       callback();
  64.       if (!is_loop_) {
  65.         break;
  66.       }
  67.     }
  68.     is_expired_ = true;
  69.     try_to_expire_ = false;
  70.   });
  71.   return true;
  72. }
  73. void SafeTimer::TryExpire() noexcept {
  74.   try_to_expire_ = true;
  75.   DestroyThread();
  76.   try_to_expire_ = false;
  77. }
  78. void SafeTimer::DestroyThread() noexcept {
  79.   if (thread_) {
  80.     {
  81.       std::lock_guard<std::mutex> lock(mutex_);
  82.       condition_.notify_all();
  83.     }
  84.     if (thread_->joinable()) {
  85.       thread_->join();
  86.     }
  87.     thread_.reset();
  88.   }
  89. }
复制代码
3. 工作流程图

     这个流程图分别展示了 SingleShot 和 Repeat 的流程,同时包罗了暂停、恢复和取消操作。
4. 单元测试

为了验证 SafeTimer 的功能,我们编写了一组单元测试,覆盖了定时器的各种使用场景,包罗单次触发、重复触发、暂停、恢复和取消等功能。
  1. #include <gmock/gmock.h>
  2. #include <gtest/gtest.h>
  3. #include <chrono>
  4. #include <thread>
  5. #include "safe_timer.h"
  6. class CallbackMock {
  7. public:
  8.   MOCK_METHOD(void, CallbackMethod, ());
  9. };
  10. class SafeTimerTest : public testing::Test {
  11. protected:
  12.   CallbackMock callback_mock;
  13.   void SetUp() override {
  14.     // Do nothing now
  15.   }
  16.   void TearDown() override {
  17.     // Do nothing now
  18.   }
  19. };
  20. TEST_F(SafeTimerTest, SingleShot) {
  21.   SafeTimer timer("TestSingleShot");
  22.   EXPECT_CALL(callback_mock, CallbackMethod()).Times(1);
  23.   int time_ms = 100;  // Delay time in milliseconds
  24.   bool ret = timer.SingleShot(time_ms, &CallbackMock::CallbackMethod, &callback_mock);
  25.   EXPECT_TRUE(ret);
  26.   // Sleep for an additional 100ms to ensure execution
  27.   std::this_thread::sleep_for(std::chrono::milliseconds(time_ms + 100));
  28. }
  29. TEST_F(SafeTimerTest, RepeatWithParamCallImmediately) {
  30.   SafeTimer timer("TestRepeatWithParamCallImmediately");
  31.   int repeat_count = 3;  // Number of times repeat should execute
  32.   int time_ms = 200;     // Delay time in milliseconds
  33.   EXPECT_CALL(callback_mock, CallbackMethod()).Times(repeat_count);
  34.   // Execute once immediately
  35.   auto ret = timer.Repeat(time_ms, true, &CallbackMock::CallbackMethod, &callback_mock);
  36.   EXPECT_TRUE(ret);
  37.   // Sleep for an additional 100ms to ensure execution
  38.   std::this_thread::sleep_for(std::chrono::milliseconds((repeat_count - 1) * time_ms + 100));
  39.   // Cancel previous timer
  40.   timer.Cancel();
  41.   EXPECT_CALL(callback_mock, CallbackMethod()).Times(repeat_count);
  42.   // Do not execute immediately
  43.   ret = timer.Repeat(time_ms, false, &CallbackMock::CallbackMethod, &callback_mock);
  44.   EXPECT_TRUE(ret);
  45.   // Sleep for an additional 100ms to ensure execution
  46.   std::this_thread::sleep_for(std::chrono::milliseconds(repeat_count * time_ms + 100));
  47. }
  48. TEST_F(SafeTimerTest, RepeatWithoutParamCallImmediately) {
  49.   SafeTimer timer("TestRepeatWithoutParamCallImmediately");
  50.   int repeat_count = 3;  // Number of times repeat should execute
  51.   int time_ms = 500;     // Delay time in milliseconds
  52.   EXPECT_CALL(callback_mock, CallbackMethod()).Times(repeat_count);
  53.   auto ret = timer.Repeat(time_ms, &CallbackMock::CallbackMethod, &callback_mock);
  54.   EXPECT_TRUE(ret);
  55.   // Sleep for an additional 100ms to ensure execution
  56.   std::this_thread::sleep_for(std::chrono::milliseconds(repeat_count * time_ms + 100));
  57. }
  58. TEST_F(SafeTimerTest, Cancel) {
  59.   SafeTimer timer("Cancel");
  60.   int repeat_count = 3;  // Number of times repeat should execute
  61.   int time_ms = 500;  // Delay time in milliseconds
  62.   EXPECT_CALL(callback_mock, CallbackMethod()).Times(repeat_count - 1);
  63.   // Execute once immediately
  64.   auto ret = timer.Repeat(time_ms, true, &CallbackMock::CallbackMethod, &callback_mock);
  65.   EXPECT_TRUE(ret);
  66.   // Sleep for 100ms less to ensure cancel is called in time
  67.   std::this_thread::sleep_for(std::chrono::milliseconds((repeat_count - 1) * time_ms - 100));
  68.   timer.Cancel();
  69. }
  70. // Test if cancelling immediately after timer creation causes any issues
  71. // Expected: Cancelling immediately after timer creation should directly return and perform no operation
  72. TEST_F(SafeTimerTest, CancelBeforeSingleShot) {
  73.   SafeTimer timer("TestCancelBeforeSingleShot");
  74.   EXPECT_CALL(callback_mock, CallbackMethod()).Times(1);
  75.   timer.Cancel();
  76.   int time_ms = 100;  // Delay time in milliseconds
  77.   auto ret = timer.SingleShot(time_ms, &CallbackMock::CallbackMethod, &callback_mock);
  78.   EXPECT_TRUE(ret);
  79.   // Sleep for an additional 100ms to ensure execution
  80.   std::this_thread::sleep_for(std::chrono::milliseconds(time_ms + 100));
  81. }
  82. // Test if cancelling immediately after creating a SingleShot timer causes any issues
  83. // Expected: Properly cancel without issues
  84. TEST_F(SafeTimerTest, CancelImmediatelyAfterSingleShot) {
  85.   SafeTimer timer("TestCancelImmediatelyAfterSingleShot");
  86.   EXPECT_CALL(callback_mock, CallbackMethod()).Times(0);
  87.   int time_ms = 100;  // Delay time in milliseconds
  88.   timer.SingleShot(time_ms, &CallbackMock::CallbackMethod, &callback_mock);
  89.   timer.Cancel();
  90.   // Sleep for an additional 100ms to ensure callback is not called
  91.   std::this_thread::sleep_for(std::chrono::milliseconds(time_ms + 100));
  92. }
  93. TEST_F(SafeTimerTest, CancelAfterSingleShot) {
  94.   SafeTimer timer("TestCancelAfterSingleShot");
  95.   EXPECT_CALL(callback_mock, CallbackMethod()).Times(1);
  96.   int time_ms = 100;  // Delay time in milliseconds
  97.   auto ret = timer.SingleShot(time_ms, &CallbackMock::CallbackMethod, &callback_mock);
  98.   EXPECT_TRUE(ret);
  99.   // Sleep for an additional 100ms to ensure execution
  100.   std::this_thread::sleep_for(std::chrono::milliseconds(time_ms + 100));
  101.   timer.Cancel();
  102. }
  103. TEST_F(SafeTimerTest, Pause) {
  104.   SafeTimer timer("Pause");
  105.   int repeat_count = 2;  // Number of times repeat should execute
  106.   int time_ms = 500;  // Delay time in milliseconds
  107.   EXPECT_CALL(callback_mock, CallbackMethod()).Times(repeat_count - 1);
  108.   // Execute once immediately
  109.   timer.Repeat(time_ms, true, &CallbackMock::CallbackMethod, &callback_mock);
  110.   // Sleep for 100ms less to ensure pause is called in time
  111.   std::this_thread::sleep_for(std::chrono::milliseconds((repeat_count - 1) * time_ms - 100));
  112.   auto ret = timer.Pause();
  113.   EXPECT_TRUE(ret);
  114. }
  115. TEST_F(SafeTimerTest, Resume) {
  116.   SafeTimer timer("Resume");
  117.   int repeat_count = 3;  // Number of times repeat should execute
  118.   int time_ms = 100;  // Delay time in milliseconds
  119.   EXPECT_CALL(callback_mock, CallbackMethod()).Times(repeat_count);
  120.   // Execute once immediately
  121.   timer.Repeat(time_ms, true, &CallbackMock::CallbackMethod, &callback_mock);
  122.   int time_advance_pause = 50;  // Time in milliseconds to pause in advance
  123.   // Sleep for time_advance_pause ms less to ensure pause is called in time
  124.   std::this_thread::sleep_for(std::chrono::milliseconds((repeat_count - 1) * time_ms - time_advance_pause));
  125.   timer.Pause();
  126.   timer.Resume();
  127.   // Sleep for an additional 100ms to ensure timer execution is completed
  128.   std::this_thread::sleep_for(std::chrono::milliseconds(time_advance_pause + 100));
  129. }
  130. int main(int argc, char** argv) {
  131.   testing::InitGoogleMock(&argc, argv);
  132.   return RUN_ALL_TESTS();
  133. }
复制代码
以上代码是使用Google Test和Google Mock进行单元测试,以下是几项要点:
每个测试都使用EXPECT_CALL设置了预期的回调调用次数,并在恰当的延时时间后查抄回调是否按预期执行。
执行结果:
  1. $ ./safe_timer_test
  2. [==========] Running 9 tests from 1 test suite.
  3. [----------] Global test environment set-up.
  4. [----------] 9 tests from SafeTimerTest
  5. [ RUN      ] SafeTimerTest.SingleShot
  6. [       OK ] SafeTimerTest.SingleShot (200 ms)
  7. [ RUN      ] SafeTimerTest.RepeatWithParamCallImmediately
  8. [       OK ] SafeTimerTest.RepeatWithParamCallImmediately (1201 ms)
  9. [ RUN      ] SafeTimerTest.RepeatWithoutParamCallImmediately
  10. [       OK ] SafeTimerTest.RepeatWithoutParamCallImmediately (1600 ms)
  11. [ RUN      ] SafeTimerTest.Cancel
  12. [       OK ] SafeTimerTest.Cancel (900 ms)
  13. [ RUN      ] SafeTimerTest.CancelBeforeSingleShot
  14. [       OK ] SafeTimerTest.CancelBeforeSingleShot (200 ms)
  15. [ RUN      ] SafeTimerTest.CancelImmediatelyAfterSingleShot
  16. [       OK ] SafeTimerTest.CancelImmediatelyAfterSingleShot (201 ms)
  17. [ RUN      ] SafeTimerTest.CancelAfterSingleShot
  18. [       OK ] SafeTimerTest.CancelAfterSingleShot (200 ms)
  19. [ RUN      ] SafeTimerTest.Pause
  20. [       OK ] SafeTimerTest.Pause (400 ms)
  21. [ RUN      ] SafeTimerTest.Resume
  22. [       OK ] SafeTimerTest.Resume (300 ms)
  23. [----------] 9 tests from SafeTimerTest (5208 ms total)
  24. [----------] Global test environment tear-down
  25. [==========] 9 tests from 1 test suite ran. (5208 ms total)
  26. [  PASSED  ] 9 tests.
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4