0


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

文章目录

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
#ifndefSAFE_TIMER_H#defineSAFE_TIMER_H#include<atomic>#include<chrono>#include<condition_variable>#include<functional>#include<memory>#include<mutex>#include<string>#include<thread>// 定义SafeTimer类,用于管理定时任务classSafeTimer{public:// 构造函数,可以指定定时器的名称,默认为"SafeTimer"explicitSafeTimer(const std::string& name ="SafeTimer")noexcept;// 析构函数virtual~SafeTimer()noexcept;// 禁止复制构造和赋值操作SafeTimer(const SafeTimer&)=delete;
  SafeTimer&operator=(const SafeTimer&)=delete;// 返回定时器的名称
  std::string GetName()constnoexcept;// 返回定时器是否处于循环模式boolIsLoop()constnoexcept;// 设置一个一次性定时任务template<typenameCallable,typename... Arguments>boolSingleShot(uint64_t interval_in_millis, Callable&& func, Arguments&&... args);// 设置一个可重复的定时任务template<typenameCallable,typename... Arguments>boolRepeat(uint64_t interval_in_millis, Callable&& func, Arguments&&... args);// 设置一个可重复的定时任务,可以选择是否立即执行一次template<typenameCallable,typename... Arguments>boolRepeat(uint64_t interval_in_millis,bool call_func_immediately, Callable&& func, Arguments&&... args);// 取消当前的定时任务voidCancel()noexcept;// 暂停当前的定时任务boolPause()noexcept;// 恢复已暂停的定时任务voidResume()noexcept;// 判断定时器是否处于空闲状态boolIsTimerIdle()constnoexcept;private:// 启动定时任务的核心函数boolStart(uint64_t interval_in_millis, std::function<void()> callback,bool loop,bool callback_immediately =false);// 尝试使定时器过期,用于取消或暂停任务voidTryExpire()noexcept;// 销毁线程资源voidDestroyThread()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<typenameCallable,typename... Arguments>boolSafeTimer::SingleShot(uint64_t interval_in_millis, Callable&& func, Arguments&&... args){// 创建一个绑定的函数对象,用于延迟执行auto action = std::bind(std::forward<Callable>(func), std::forward<Arguments>(args)...);// 调用私有的Start函数,设置一次性任务returnStart(interval_in_millis, action,false);}// 循环定时任务的实现template<typenameCallable,typename... Arguments>boolSafeTimer::Repeat(uint64_t interval_in_millis, Callable&& func, Arguments&&... args){// 创建一个绑定的函数对象,用于延迟执行auto action = std::bind(std::forward<Callable>(func), std::forward<Arguments>(args)...);// 调用私有的Start函数,设置循环任务returnStart(interval_in_millis, action,true);}// 循环定时任务的实现,允许指定是否立即执行一次template<typenameCallable,typename... Arguments>boolSafeTimer::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函数,设置循环任务,可选择立即执行returnStart(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()constnoexcept{return name_;}boolSafeTimer::IsLoop()constnoexcept{return is_loop_;}voidSafeTimer::Cancel()noexcept{if(is_expired_ || try_to_expire_ ||!thread_){return;}TryExpire();}boolSafeTimer::Pause()noexcept{if(is_expired_){returnfalse;}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){returnfalse;}Cancel();
  task_remain_time_ms_ =static_cast<uint64_t>(remaining);returntrue;}voidSafeTimer::Resume()noexcept{if(task_remain_time_ms_ >0&& callback_){Start(task_remain_time_ms_, callback_,false,false);
    task_remain_time_ms_ =0;}}boolSafeTimer::IsTimerIdle()constnoexcept{return is_expired_ &&!try_to_expire_;}boolSafeTimer::Start(uint64_t interval_in_millis, std::function<void()> callback,bool loop,bool callback_immediately){if(!is_expired_ || try_to_expire_){returnfalse;}

  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;});returntrue;}voidSafeTimer::TryExpire()noexcept{
  try_to_expire_ =true;DestroyThread();
  try_to_expire_ =false;}voidSafeTimer::DestroyThread()noexcept{if(thread_){{
      std::lock_guard<std::mutex>lock(mutex_);
      condition_.notify_all();}if(thread_->joinable()){
      thread_->join();}
    thread_.reset();}}

3. 工作流程图

#mermaid-svg-WJhW9Hj6xEKTmzeM {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-WJhW9Hj6xEKTmzeM .error-icon{fill:#552222;}#mermaid-svg-WJhW9Hj6xEKTmzeM .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-WJhW9Hj6xEKTmzeM .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-WJhW9Hj6xEKTmzeM .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-WJhW9Hj6xEKTmzeM .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-WJhW9Hj6xEKTmzeM .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-WJhW9Hj6xEKTmzeM .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-WJhW9Hj6xEKTmzeM .marker{fill:#333333;stroke:#333333;}#mermaid-svg-WJhW9Hj6xEKTmzeM .marker.cross{stroke:#333333;}#mermaid-svg-WJhW9Hj6xEKTmzeM svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-WJhW9Hj6xEKTmzeM .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-WJhW9Hj6xEKTmzeM .cluster-label text{fill:#333;}#mermaid-svg-WJhW9Hj6xEKTmzeM .cluster-label span{color:#333;}#mermaid-svg-WJhW9Hj6xEKTmzeM .label text,#mermaid-svg-WJhW9Hj6xEKTmzeM span{fill:#333;color:#333;}#mermaid-svg-WJhW9Hj6xEKTmzeM .node rect,#mermaid-svg-WJhW9Hj6xEKTmzeM .node circle,#mermaid-svg-WJhW9Hj6xEKTmzeM .node ellipse,#mermaid-svg-WJhW9Hj6xEKTmzeM .node polygon,#mermaid-svg-WJhW9Hj6xEKTmzeM .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-WJhW9Hj6xEKTmzeM .node .label{text-align:center;}#mermaid-svg-WJhW9Hj6xEKTmzeM .node.clickable{cursor:pointer;}#mermaid-svg-WJhW9Hj6xEKTmzeM .arrowheadPath{fill:#333333;}#mermaid-svg-WJhW9Hj6xEKTmzeM .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-WJhW9Hj6xEKTmzeM .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-WJhW9Hj6xEKTmzeM .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-WJhW9Hj6xEKTmzeM .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-WJhW9Hj6xEKTmzeM .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-WJhW9Hj6xEKTmzeM .cluster text{fill:#333;}#mermaid-svg-WJhW9Hj6xEKTmzeM .cluster span{color:#333;}#mermaid-svg-WJhW9Hj6xEKTmzeM div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-WJhW9Hj6xEKTmzeM :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}

       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 
     

这个流程图分别展示了

SingleShot

Repeat

的流程,同时包括了暂停、恢复和取消操作。

4. 单元测试

为了验证

SafeTimer

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

#include<gmock/gmock.h>#include<gtest/gtest.h>#include<chrono>#include<thread>#include"safe_timer.h"classCallbackMock{public:MOCK_METHOD(void, CallbackMethod,());};classSafeTimerTest:public testing::Test{protected:
  CallbackMock callback_mock;voidSetUp()override{// Do nothing now}voidTearDown()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 millisecondsbool 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 executeint time_ms =200;// Delay time in millisecondsEXPECT_CALL(callback_mock,CallbackMethod()).Times(repeat_count);// Execute once immediatelyauto 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 executeint time_ms =500;// Delay time in millisecondsEXPECT_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 executeint time_ms =500;// Delay time in millisecondsEXPECT_CALL(callback_mock,CallbackMethod()).Times(repeat_count -1);// Execute once immediatelyauto 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 operationTEST_F(SafeTimerTest, CancelBeforeSingleShot){
  SafeTimer timer("TestCancelBeforeSingleShot");EXPECT_CALL(callback_mock,CallbackMethod()).Times(1);
  timer.Cancel();int time_ms =100;// Delay time in millisecondsauto 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 issuesTEST_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 millisecondsauto 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 executeint time_ms =500;// Delay time in millisecondsEXPECT_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 executeint time_ms =100;// Delay time in millisecondsEXPECT_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));}intmain(int argc,char** argv){
  testing::InitGoogleMock(&argc, argv);returnRUN_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.
标签: c++ 定时器 timer

本文转载自: https://blog.csdn.net/stallion5632/article/details/140569787
版权归原作者 橘色的喵 所有, 如有侵权,请联系我们删除。

“C++编程:实现一个跨平台安全的定时器Timer模块”的评论:

还没有评论