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.