C++编程:实现一个安全的定时器模块

文章目录

    • [0. 概要](#0. 概要)
    • [1. 设计目标](#1. 设计目标)
    • [2. `SafeTimer` 类的实现](#2. SafeTimer 类的实现)
      • [2.1 头文件 `safe_timer.h`](#2.1 头文件 safe_timer.h)
      • [源文件 `safe_timer.cpp`](#源文件 safe_timer.cpp)
    • [3. 工作流程图](#3. 工作流程图)
    • [4. 单元测试](#4. 单元测试)

0. 概要

在嵌入式系统和高性能应用中,定时器模块是一个至关重要的组件。为了确保系统的可靠性和功能安全,我们需要设计一个高效、稳定的定时器。

本文将实现一个安全的C++ SafeTimer 定时器模块,并提供完整的gtest单元测试。

完整代码见 gitee_safe_timer

类似设计请参考之前的文章C++11 异步执行函数的封装,异步执行阻塞或CPU密集型应用,延迟执行函数

1. 设计目标

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

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

2. SafeTimer 类的实现

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

2.1 头文件 safe_timer.h

cpp 复制代码
#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

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. 工作流程图

Pause/Resume/Cancel Repeat SingleShot SingleShot Yes No Repeat Yes Yes No No Pause Resume Cancel Pause/Resume/Cancel Pause Timer Resume Timer Cancel Timer Set Repeat Timer Start Timer Thread Wait for Timeout Timer Expired? Execute Callback Loop? End Set SingleShot Timer Create Timer Start Timer Thread Wait for Timeout Timer Expired? Execute Callback End Start

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

4. 单元测试

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

cpp 复制代码
#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();
}

执行结果:

复制代码
$ ./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.
相关推荐
Black蜡笔小新13 分钟前
视频融合平台EasyCVR构筑智慧小区安全防护与智能管理的数字底座
安全·音视频
晨晖222 分钟前
直接插入排序
c语言·数据结构·c++·算法
Hilaku24 分钟前
2025快手直播至暗时刻:当黑产自动化洪流击穿P0防线,我们前端能做什么?🤷‍♂️
前端·javascript·安全
思通数据25 分钟前
AI智能预警系统:矿山、工厂与油气站安全管理架构浅析
人工智能·深度学习·安全·目标检测·机器学习·计算机视觉·架构
郝学胜-神的一滴31 分钟前
Linux的pthread_self函数详解:多线程编程中的身份标识器
linux·运维·服务器·开发语言·c++·程序人生
oioihoii37 分钟前
C++高并发编程核心技能解析
开发语言·c++
玖剹40 分钟前
字符串相关题目
c语言·c++·算法·leetcode
千里马-horse1 小时前
AsyncContext
开发语言·前端·javascript·c++·napi·asynccontext
数字护盾(和中)1 小时前
BAS模拟入侵攻击系统:前置防控核心,守护企业网络安全
网络·安全·web安全
宠..1 小时前
为单选按钮绑定事件
运维·服务器·开发语言·数据库·c++·qt·microsoft