0
点赞
收藏
分享

微信扫一扫

发现FionaAI:免费体验最新的GPT-4o Mini模型!

文章目录

0. 概要

对于C++应用编程,定时器模块是一个至关重要的组件。为了确保系统的可靠性和功能安全,我们需要设计一个高效、稳定的定时器。
本文将实现一个跨平台安全的C++ SafeTimer 定时器模块,并提供完整的gtest单元测试。

完整代码见 gitee_safe_timer

类似设计请参阅文章:C++编程: 线程池封装、任务异步执行以及任务延迟执行

1. 设计目标

目标是创建一个符合功能安全要求的定时器模块,具体包括以下几点:

  1. 线程安全:确保多线程环境下的安全性。
  2. 高可靠性:在异常情况下能够安全地停止定时器。
  3. 高可维护性:代码结构清晰,易于扩展和维护。

2. SafeTimer 类的实现

SafeTimer 类是我们实现的核心,它提供了单次触发(SingleShot)和重复触发(Repeat)两种定时功能,同时还支持暂停(Pause)和恢复(Resume)。以下是 SafeTimer 类的完整实现。

2.1 头文件 safe_timer.h

#ifndef SAFE_TIMER_H
#define SAFE_TIMER_H

#include <atomic>
#include <chrono>
#include <condition_variable>
#include <functional>
#include <memory>
#include <mutex>
#include <string>
#include <thread>

// 定义SafeTimer类,用于管理定时任务
class SafeTimer {
 public:
  // 构造函数,可以指定定时器的名称,默认为"SafeTimer"
  explicit SafeTimer(const std::string& name = "SafeTimer") noexcept;

  // 析构函数
  virtual ~SafeTimer() noexcept;

  // 禁止复制构造和赋值操作
  SafeTimer(const SafeTimer&) = delete;
  SafeTimer& operator=(const SafeTimer&) = delete;

  // 返回定时器的名称
  std::string GetName() const noexcept;

  // 返回定时器是否处于循环模式
  bool IsLoop() const noexcept;

  // 设置一个一次性定时任务
  template <typename Callable, typename... Arguments>
  bool SingleShot(uint64_t interval_in_millis, Callable&& func, Arguments&&... args);

  // 设置一个可重复的定时任务
  template <typename Callable, typename... Arguments>
  bool Repeat(uint64_t interval_in_millis, Callable&& func, Arguments&&... args);

  // 设置一个可重复的定时任务,可以选择是否立即执行一次
  template <typename Callable, typename... Arguments>
  bool Repeat(uint64_t interval_in_millis, bool call_func_immediately, Callable&& func, Arguments&&... args);

  // 取消当前的定时任务
  void Cancel() noexcept;

  // 暂停当前的定时任务
  bool Pause() noexcept;

  // 恢复已暂停的定时任务
  void Resume() noexcept;

  // 判断定时器是否处于空闲状态
  bool IsTimerIdle() const noexcept;

 private:
  // 启动定时任务的核心函数
  bool Start(uint64_t interval_in_millis, std::function<void()> callback, bool loop, bool callback_immediately = false);

  // 尝试使定时器过期,用于取消或暂停任务
  void TryExpire() noexcept;

  // 销毁线程资源
  void DestroyThread() noexcept;

 private:
  // 定时器的名称
  std::string name_;

  // 标记定时器是否为循环模式
  bool is_loop_;

  // 原子布尔类型,标记定时器是否已经过期
  std::atomic_bool is_expired_;

  // 原子布尔类型,标记是否尝试使定时器过期
  std::atomic_bool try_to_expire_;

  // 独占所有权的线程智能指针
  std::unique_ptr<std::thread> thread_;

  // 互斥锁,用于线程同步
  std::mutex mutex_;

  // 条件变量,用于线程间的通信
  std::condition_variable condition_;

  // 定时器启动时的时间点
  std::chrono::time_point<std::chrono::steady_clock> start_time_;

  // 定时器结束时的时间点
  std::chrono::time_point<std::chrono::steady_clock> end_time_;

  // 剩余任务时间(毫秒)
  uint64_t task_remain_time_ms_;

  // 回调函数,当定时器过期时调用
  std::function<void()> callback_;
};

// 实现模板成员函数

// 单次定时任务的实现
template <typename Callable, typename... Arguments>
bool SafeTimer::SingleShot(uint64_t interval_in_millis, Callable&& func, Arguments&&... args) {
  // 创建一个绑定的函数对象,用于延迟执行
  auto action = std::bind(std::forward<Callable>(func), std::forward<Arguments>(args)...);
  // 调用私有的Start函数,设置一次性任务
  return Start(interval_in_millis, action, false);
}

// 循环定时任务的实现
template <typename Callable, typename... Arguments>
bool SafeTimer::Repeat(uint64_t interval_in_millis, Callable&& func, Arguments&&... args) {
  // 创建一个绑定的函数对象,用于延迟执行
  auto action = std::bind(std::forward<Callable>(func), std::forward<Arguments>(args)...);
  // 调用私有的Start函数,设置循环任务
  return Start(interval_in_millis, action, true);
}

// 循环定时任务的实现,允许指定是否立即执行一次
template <typename Callable, typename... Arguments>
bool SafeTimer::Repeat(uint64_t interval_in_millis, bool call_func_immediately, Callable&& func, Arguments&&... args) {
  // 创建一个绑定的函数对象,用于延迟执行
  auto action = std::bind(std::forward<Callable>(func), std::forward<Arguments>(args)...);
  // 调用私有的Start函数,设置循环任务,可选择立即执行
  return Start(interval_in_millis, action, true, call_func_immediately);
}

#endif  // SAFE_TIMER_H

源文件 safe_timer.cpp

#include "safe_timer.h"
#include <iostream>

SafeTimer::SafeTimer(const std::string& name) noexcept
    : name_(name), is_loop_(false), is_expired_(true), try_to_expire_(false), task_remain_time_ms_(0), callback_(nullptr) {}

SafeTimer::~SafeTimer() noexcept {
  TryExpire();
}

std::string SafeTimer::GetName() const noexcept {
  return name_;
}

bool SafeTimer::IsLoop() const noexcept {
  return is_loop_;
}

void SafeTimer::Cancel() noexcept {
  if (is_expired_ || try_to_expire_ || !thread_) {
    return;
  }
  TryExpire();
}

bool SafeTimer::Pause() noexcept {
  if (is_expired_) {
    return false;
  }

  auto now = std::chrono::steady_clock::now();
  auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - start_time_).count();
  auto remaining = std::chrono::duration_cast<std::chrono::milliseconds>(end_time_ - now).count();

  if (remaining <= 0) {
    return false;
  }

  Cancel();
  task_remain_time_ms_ = static_cast<uint64_t>(remaining);
  return true;
}

void SafeTimer::Resume() noexcept {
  if (task_remain_time_ms_ > 0 && callback_) {
    Start(task_remain_time_ms_, callback_, false, false);
    task_remain_time_ms_ = 0;
  }
}

bool SafeTimer::IsTimerIdle() const noexcept {
  return is_expired_ && !try_to_expire_;
}

bool SafeTimer::Start(uint64_t interval_in_millis, std::function<void()> callback, bool loop, bool callback_immediately) {
  if (!is_expired_ || try_to_expire_) {
    return false;
  }

  is_expired_ = false;
  is_loop_ = loop;

  DestroyThread();
  thread_ = std::make_unique<std::thread>([this, interval_in_millis, callback, callback_immediately]() {
    if (callback_immediately) {
      callback();
    }

    while (!try_to_expire_) {
      callback_ = callback;
      start_time_ = std::chrono::steady_clock::now();
      end_time_ = start_time_ + std::chrono::milliseconds(interval_in_millis);

      std::unique_lock<std::mutex> lock(mutex_);
      condition_.wait_until(lock, end_time_);

      if (try_to_expire_) {
        break;
      }
      callback();
      if (!is_loop_) {
        break;
      }
    }

    is_expired_ = true;
    try_to_expire_ = false;
  });

  return true;
}

void SafeTimer::TryExpire() noexcept {
  try_to_expire_ = true;
  DestroyThread();
  try_to_expire_ = false;
}

void SafeTimer::DestroyThread() noexcept {
  if (thread_) {
    {
      std::lock_guard<std::mutex> lock(mutex_);
      condition_.notify_all();
    }
    if (thread_->joinable()) {
      thread_->join();
    }
    thread_.reset();
  }
}

3. 工作流程图

这个流程图分别展示了 SingleShotRepeat 的流程,同时包括了暂停、恢复和取消操作。

4. 单元测试

为了验证 SafeTimer 的功能,我们编写了一组单元测试,覆盖了定时器的各种使用场景,包括单次触发、重复触发、暂停、恢复和取消等功能。

#include <gmock/gmock.h>
#include <gtest/gtest.h>

#include <chrono>
#include <thread>

#include "safe_timer.h"

class CallbackMock {
 public:
  MOCK_METHOD(void, CallbackMethod, ());
};

class SafeTimerTest : public testing::Test {
 protected:
  CallbackMock callback_mock;

  void SetUp() override {
    // Do nothing now
  }

  void TearDown() override {
    // Do nothing now
  }
};

TEST_F(SafeTimerTest, SingleShot) {
  SafeTimer timer("TestSingleShot");
  EXPECT_CALL(callback_mock, CallbackMethod()).Times(1);
  int time_ms = 100;  // Delay time in milliseconds
  bool ret = timer.SingleShot(time_ms, &CallbackMock::CallbackMethod, &callback_mock);
  EXPECT_TRUE(ret);
  // Sleep for an additional 100ms to ensure execution
  std::this_thread::sleep_for(std::chrono::milliseconds(time_ms + 100));
}

TEST_F(SafeTimerTest, RepeatWithParamCallImmediately) {
  SafeTimer timer("TestRepeatWithParamCallImmediately");
  int repeat_count = 3;  // Number of times repeat should execute
  int time_ms = 200;     // Delay time in milliseconds

  EXPECT_CALL(callback_mock, CallbackMethod()).Times(repeat_count);
  // Execute once immediately
  auto ret = timer.Repeat(time_ms, true, &CallbackMock::CallbackMethod, &callback_mock);
  EXPECT_TRUE(ret);
  // Sleep for an additional 100ms to ensure execution
  std::this_thread::sleep_for(std::chrono::milliseconds((repeat_count - 1) * time_ms + 100));

  // Cancel previous timer
  timer.Cancel();
  EXPECT_CALL(callback_mock, CallbackMethod()).Times(repeat_count);
  // Do not execute immediately
  ret = timer.Repeat(time_ms, false, &CallbackMock::CallbackMethod, &callback_mock);
  EXPECT_TRUE(ret);
  // Sleep for an additional 100ms to ensure execution
  std::this_thread::sleep_for(std::chrono::milliseconds(repeat_count * time_ms + 100));
}

TEST_F(SafeTimerTest, RepeatWithoutParamCallImmediately) {
  SafeTimer timer("TestRepeatWithoutParamCallImmediately");
  int repeat_count = 3;  // Number of times repeat should execute
  int time_ms = 500;     // Delay time in milliseconds

  EXPECT_CALL(callback_mock, CallbackMethod()).Times(repeat_count);
  auto ret = timer.Repeat(time_ms, &CallbackMock::CallbackMethod, &callback_mock);
  EXPECT_TRUE(ret);
  // Sleep for an additional 100ms to ensure execution
  std::this_thread::sleep_for(std::chrono::milliseconds(repeat_count * time_ms + 100));
}

TEST_F(SafeTimerTest, Cancel) {
  SafeTimer timer("Cancel");
  int repeat_count = 3;  // Number of times repeat should execute
  int time_ms = 500;  // Delay time in milliseconds

  EXPECT_CALL(callback_mock, CallbackMethod()).Times(repeat_count - 1);
  // Execute once immediately
  auto ret = timer.Repeat(time_ms, true, &CallbackMock::CallbackMethod, &callback_mock);
  EXPECT_TRUE(ret);
  // Sleep for 100ms less to ensure cancel is called in time
  std::this_thread::sleep_for(std::chrono::milliseconds((repeat_count - 1) * time_ms - 100));
  timer.Cancel();
}

// Test if cancelling immediately after timer creation causes any issues
// Expected: Cancelling immediately after timer creation should directly return and perform no operation
TEST_F(SafeTimerTest, CancelBeforeSingleShot) {
  SafeTimer timer("TestCancelBeforeSingleShot");
  EXPECT_CALL(callback_mock, CallbackMethod()).Times(1);
  timer.Cancel();
  int time_ms = 100;  // Delay time in milliseconds
  auto ret = timer.SingleShot(time_ms, &CallbackMock::CallbackMethod, &callback_mock);
  EXPECT_TRUE(ret);
  // Sleep for an additional 100ms to ensure execution
  std::this_thread::sleep_for(std::chrono::milliseconds(time_ms + 100));
}

// Test if cancelling immediately after creating a SingleShot timer causes any issues
// Expected: Properly cancel without issues
TEST_F(SafeTimerTest, CancelImmediatelyAfterSingleShot) {
  SafeTimer timer("TestCancelImmediatelyAfterSingleShot");
  EXPECT_CALL(callback_mock, CallbackMethod()).Times(0);
  int time_ms = 100;  // Delay time in milliseconds
  timer.SingleShot(time_ms, &CallbackMock::CallbackMethod, &callback_mock);
  timer.Cancel();
  // Sleep for an additional 100ms to ensure callback is not called
  std::this_thread::sleep_for(std::chrono::milliseconds(time_ms + 100));
}

TEST_F(SafeTimerTest, CancelAfterSingleShot) {
  SafeTimer timer("TestCancelAfterSingleShot");
  EXPECT_CALL(callback_mock, CallbackMethod()).Times(1);
  int time_ms = 100;  // Delay time in milliseconds
  auto ret = timer.SingleShot(time_ms, &CallbackMock::CallbackMethod, &callback_mock);
  EXPECT_TRUE(ret);
  // Sleep for an additional 100ms to ensure execution
  std::this_thread::sleep_for(std::chrono::milliseconds(time_ms + 100));
  timer.Cancel();
}

TEST_F(SafeTimerTest, Pause) {
  SafeTimer timer("Pause");
  int repeat_count = 2;  // Number of times repeat should execute
  int time_ms = 500;  // Delay time in milliseconds

  EXPECT_CALL(callback_mock, CallbackMethod()).Times(repeat_count - 1);
  // Execute once immediately
  timer.Repeat(time_ms, true, &CallbackMock::CallbackMethod, &callback_mock);
  // Sleep for 100ms less to ensure pause is called in time
  std::this_thread::sleep_for(std::chrono::milliseconds((repeat_count - 1) * time_ms - 100));
  auto ret = timer.Pause();
  EXPECT_TRUE(ret);
}

TEST_F(SafeTimerTest, Resume) {
  SafeTimer timer("Resume");
  int repeat_count = 3;  // Number of times repeat should execute
  int time_ms = 100;  // Delay time in milliseconds

  EXPECT_CALL(callback_mock, CallbackMethod()).Times(repeat_count);
  // Execute once immediately
  timer.Repeat(time_ms, true, &CallbackMock::CallbackMethod, &callback_mock);
  int time_advance_pause = 50;  // Time in milliseconds to pause in advance
  // Sleep for time_advance_pause ms less to ensure pause is called in time
  std::this_thread::sleep_for(std::chrono::milliseconds((repeat_count - 1) * time_ms - time_advance_pause));
  timer.Pause();
  timer.Resume();
  // Sleep for an additional 100ms to ensure timer execution is completed
  std::this_thread::sleep_for(std::chrono::milliseconds(time_advance_pause + 100));
}

int main(int argc, char** argv) {
  testing::InitGoogleMock(&argc, argv);
  return RUN_ALL_TESTS();
}

以上代码是使用Google Test和Google Mock进行单元测试,以下是几项要点:

  1. 单次触发测试

    • SingleShot测试了SafeTimer在设定的延时后只触发一次CallbackMethod
  2. 重复触发测试

    • RepeatWithParamCallImmediately测试了计时器立即执行并重复触发回调的功能。
    • RepeatWithoutParamCallImmediately测试了计时器不立即执行,仅按照设定间隔重复触发回调的功能。
  3. 取消计时器测试

    • Cancel测试了在计时器执行过程中取消操作是否有效。
    • CancelBeforeSingleShot测试了在单次触发计时器创建后立即取消是否有效。
    • CancelImmediatelyAfterSingleShot测试了在单次触发计时器执行前立即取消的效果。
    • CancelAfterSingleShot测试了在单次触发计时器执行后再取消的效果。
  4. 暂停与恢复计时器测试

    • Pause测试了暂停计时器的功能。
    • Resume测试了暂停后恢复计时器的功能。

每个测试都使用EXPECT_CALL设置了预期的回调调用次数,并在适当的延时时间后检查回调是否按预期执行。

执行结果:

$ ./safe_timer_test 
[==========] Running 9 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 9 tests from SafeTimerTest
[ RUN      ] SafeTimerTest.SingleShot
[       OK ] SafeTimerTest.SingleShot (200 ms)
[ RUN      ] SafeTimerTest.RepeatWithParamCallImmediately
[       OK ] SafeTimerTest.RepeatWithParamCallImmediately (1201 ms)
[ RUN      ] SafeTimerTest.RepeatWithoutParamCallImmediately
[       OK ] SafeTimerTest.RepeatWithoutParamCallImmediately (1600 ms)
[ RUN      ] SafeTimerTest.Cancel
[       OK ] SafeTimerTest.Cancel (900 ms)
[ RUN      ] SafeTimerTest.CancelBeforeSingleShot
[       OK ] SafeTimerTest.CancelBeforeSingleShot (200 ms)
[ RUN      ] SafeTimerTest.CancelImmediatelyAfterSingleShot
[       OK ] SafeTimerTest.CancelImmediatelyAfterSingleShot (201 ms)
[ RUN      ] SafeTimerTest.CancelAfterSingleShot
[       OK ] SafeTimerTest.CancelAfterSingleShot (200 ms)
[ RUN      ] SafeTimerTest.Pause
[       OK ] SafeTimerTest.Pause (400 ms)
[ RUN      ] SafeTimerTest.Resume
[       OK ] SafeTimerTest.Resume (300 ms)
[----------] 9 tests from SafeTimerTest (5208 ms total)

[----------] Global test environment tear-down
[==========] 9 tests from 1 test suite ran. (5208 ms total)
[  PASSED  ] 9 tests.
举报

相关推荐

0 条评论