0. 概要
对于C++应用编程,定时器模块是一个至关重要的组件。为了确保系统的可靠性和功能安全,我们需要设计一个高效、稳固的定时器。
本文将实现一个跨平台安全的C++ SafeTimer 定时器模块,并提供完备的gtest单元测试。
完备代码见 gitee_safe_timer
雷同设计请参阅文章:C++编程: 线程池封装、使命异步执行以及使命延迟执行
1. 设计目标
目标是创建一个符合功能安全要求的定时器模块,具体包罗以下几点:
- 线程安全:确保多线程环境下的安全性。
- 高可靠性:在异常情况下能够安全地停止定时器。
- 高可维护性:代码结构清楚,易于扩展和维护。
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. 工作流程图
这个流程图分别展示了 SingleShot 和 Repeat 的流程,同时包罗了暂停、恢复和取消操作。
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进行单元测试,以下是几项要点:
- 单次触发测试:
- SingleShot测试了SafeTimer在设定的延时后只触发一次CallbackMethod。
- 重复触发测试:
- RepeatWithParamCallImmediately测试了计时器立即执行并重复触发回调的功能。
- RepeatWithoutParamCallImmediately测试了计时器不立即执行,仅按照设定隔断重复触发回调的功能。
- 取消计时器测试:
- Cancel测试了在计时器执行过程中取消操作是否有效。
- CancelBeforeSingleShot测试了在单次触发计时器创建后立即取消是否有效。
- CancelImmediatelyAfterSingleShot测试了在单次触发计时器执行前立即取消的效果。
- CancelAfterSingleShot测试了在单次触发计时器执行后再取消的效果。
- 暂停与恢复计时器测试:
- 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.
复制代码 免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |