首页 > 编程语言 >C++ 使用单调时钟按一定时间间隔执行任务

C++ 使用单调时钟按一定时间间隔执行任务

时间:2024-01-31 17:36:17浏览次数:30  
标签:std chrono clock pthread C++ cond time 单调 时钟

使用condition_variable实现定时执行任务

遇到一个开发任务,需要按一定的时间间隔执行任务,本来是一个简单的功能,直接使用condition_variable就可以了

最开始是直接使用condition_variable实现的定时触发机制, 代码的大致实现类似于:

#include <condition_variable>
#include <chrono>

typedef std::chrono::std::chrono::system_clockClockType;
typedef std::chrono::time_point<ClockType> TimePointType;

void main()
{
    TimePointType currTime = ClockType::now();
    TimePointType awaitTimePoint = currTime;
    int m_nCheckInterval = 10;

    std::condition_variable m_cv;
    std::mutex m_mut;
    bool m_bExit;
    
    while(!m_bExit)
    {
        currTime = ClockType::now();
        if (currTime <= m_awaitTimePoint)
        {
            if(m_cv.wait_until(lock, m_awaitTimePoint, [this](){ 
                return m_bExit;
              }))
            {
               if(m_bExit){ break; }
            }
        }

        if(m_bExit){ break; }

        // compute next await timepoint
        m_awaitTimePoint = m_awaitTimePoint + std::chrono::seconds(m_nCheckInterval);
        
        // do something ... 
    };
}

当系统时间发生变化时,等待间隔发生变化

但是在系统时间发生变化的时候出现了问题:

比如原本打算在16:30分唤醒程序继续执行,当前的系统时间为16:25,此时程序阻塞,等待5分钟后唤醒
然后由于各种原因,比如NTP自动调整时间或者手动设置系统时间,将系统时间修改为16:24
程序还是需要等待到16:30分才会被唤醒,此时需要等待6分钟

在这里不管是使用wait_for还是wait_util实际上是没有区别的:wait_for在实现上是依赖于wait_util
condition_variable/wait_for

Equivalent to return wait_until(lock, std::chrono::steady_clock::now() + rel_time, std::move(stop_waiting));. This overload may be used to ignore spurious awakenings by looping until some predicate is satisfied (bool(stop_waiting()) == true).

使用系统时钟std::chrono::system_clock会有问题,要让时间间隔不受系统时间的影响,应该是可以使用单调时钟来实现的
std::chrono提供了三种类型的时钟:

std::chrono::system_clock;
std::chrono::high_resolution_clock;
std::chrono::steady_clock;

其中std::chrono::steady_clock就是单调时钟
std::chrono::steady_clock

Class std::chrono::steady_clock represents a monotonic clock. The time points of this clock cannot decrease as physical time moves forward and the time between ticks of this clock is constant. This clock is not related to wall clock time (for example, it can be time since last reboot), and is most suitable for measuring intervals.

但是不幸的是,标准库在POSIX上的实现存在问题——不管使用的时钟类型是什么,实际用的都是std::chrono::system_clock

之前已经有人提出过这个问题了:https://www.open-std.org/jtc1/sc22/wg21/docs/lwg-closed.html#887Correct C++11 std::condition_variable requires a version of pthread_cond_timedwait that supports specifying the clock
后来被放在Adding clockid parameter to functions that accept absolute struct timespec timeouts,并最终在glibc 2.30 修复

使用使用pthread_cond_timedwaitCLOCK_MONOTONIC定时触发

但是升级glibc比较麻烦,特别是在生产环境上,所以需要使用替代的方案

其实wait_util在Linux上的实现是使用pthread_cond_timedwait来实现的,而pthread_cond_timedwait设置的时钟也有多个选择:
https://linux.die.net/man/3/clock_gettime

CLOCK_REALTIME
System-wide realtime clock. Setting this clock requires appropriate privileges.
CLOCK_MONOTONIC
Clock that cannot be set and represents monotonic time since some unspecified starting point.
CLOCK_PROCESS_CPUTIME_ID
High-resolution per-process timer from the CPU.
CLOCK_THREAD_CPUTIME_ID
Thread-specific CPU-time clock.

实际上标准库提供的各种时钟类型也是使用上面的这些时钟类型进行的封装,单调时钟对应的是CLOCK_MONOTONIC

所以改成了使用pthread_cond_timedwaitCLOCK_MONOTONIC实现按照一定的时间间隔定时触发任务

void main()
{
    pthread_mutex_t m_mutex;
    pthread_condattr_t m_attr;
    pthread_cond_t m_cond;

    pthread_condattr_init(&m_attr);
    pthread_condattr_setclock(&m_attr, CLOCK_MONOTONIC);    
    pthread_cond_init(&m_cond, &m_attr);
    
    int m_nCheckInterval = 10;

    struct timespec start_mono_time;
    clock_gettime(CLOCK_MONOTONIC, &start_mono_time);
    struct timespec target_mono_time = start_mono_time;
    
    while(!m_bExit)
    {
        clock_gettime(CLOCK_MONOTONIC, &start_mono_time);
        if (start_mono_time.tv_sec < target_mono_time.tv_sec)
        {
            pthread_mutex_lock(&m_mutex);
            if(m_bExit){ break; }
            int res = pthread_cond_timedwait(&m_cond, &m_mutex, &target_mono_time);
            if(res != ETIMEDOUT)
            {
                if(m_bExit){ pthread_mutex_unlock(&m_mutex); break; }
            }
            pthread_mutex_unlock(&m_mutex);
        }

        if(m_bExit){ break; }

        // compute next await timepoint
        target_mono_time.tv_sec = target_mono_time.tv_sec + m_nCheckInterval;
        
        // do something ... 
    };
}

参考资料

https://stackoverflow.com/questions/51005267/how-do-i-deal-with-the-system-clock-changing-while-waiting-on-a-stdcondition-v
https://linux.die.net/man/3/clock_gettime
检测系统时间发信变化,原贴再stack-overflow, 需要利用linux 3.0 新加的特性 https://cloud.tencent.com/developer/ask/sof/102761402
标准库提供的时钟和wait_for,wait_util
https://en.cppreference.com/w/cpp/chrono/system_clock
https://en.cppreference.com/w/cpp/chrono/steady_clock
https://en.cppreference.com/w/cpp/thread/condition_variable/wait_for
利用新特性 TFD_TIMER_CANCEL_ON_SET 检查系统时间变化的参考手册
https://man7.org/linux/man-pages/man2/timerfd_create.2.html

标签:std,chrono,clock,pthread,C++,cond,time,单调,时钟
From: https://www.cnblogs.com/muzhy/p/17999741

相关文章

  • 如何在Visual Studio新C++项目中调用之前配置过的库?
      本文介绍在VisualStudio软件中调用C++各种配置、编译完毕的第三方库的方法。  在撰写C++代码时,如果需要用到他人撰写的第三方库(例如地理数据处理库GDAL、矩阵运算库Armadillo等),并不能像Python等语言那样,安装好库后直接在不同代码文件中使用;而是需要每一次新建一个代码文件......
  • 从C向C++——运算符重载
    本文的主要知识点是C++中的运算符重载。1.运算符重载所谓重载,就是赋予新的含义。函数重载(FunctionOverloading)可以让一个函数名有多种功能,在不同情况下进行不同的操作。**运算符重载(OperatorOverloading)**也是一个道理,同一个运算符可以有不同的功能。实际上,我们已经在不知不觉中......
  • 《C++ Primer Plus》(第六版)中文版——思维导图+附录PDF+源代码
    说明,以下文件可在异步社区免费下载不同之处在于原附录PDF文件没有书签,而本文分享的附录文件带有书签本文所有文件下载链接:https://www.123pan.com/s/lO3uVv-uaEKv.html思维导图(图片)以下仅为预览,高清图片可从文章开头下载链接中下载另外后续本人有空会制作XMind脑图版本,会添加......
  • 【侯捷C++面向对象笔记】补充5-new & delete重载
    平时所使用的new和delete操作,称之为表达式,一般由好几个步骤组成。如上图所示,new表达式会被编译器转化为三个步骤。new表达式不能重载,但其中operatornew是可以重载的。➡️全局::operatornew的重载why不能放在namespace内?因为全局operatornew是放在defaultglobalnamespac......
  • 【侯捷C++面向对象笔记】补充2-pointer-like & function-like class
    关键词:仿函数pointer-like:将一个类设计得像指针一样,通常通过重载*和->操作符实现。function-like:将类的成员设计得能像函数一样使用,通过重载()操作符实现。TipDemo应用:智能指针注意:->符号在作用一次后,会继续作用下去(不同于*号)Foof(*sp):f为一个Foo对象本体,使用时f.m......
  • 【侯捷C++面向对象笔记】补充3-template
    关键词:类模板,函数模板,成员模板,模板特化“泛化”和“特化”TipDemo类模板定义时需要显式地指定类型名。函数模板定义时编译器自动进行实参推导类型(但不提供隐式转换)。成员模板:模板中还包含模板模板(全)特化格式:template<>尖括号内为空模板偏特化(partia......
  • 【侯捷C++面向对象笔记】补充4-object model
    关键词:虚函数表,动态绑定,多态每个对象都维护自己的虚表指针,指向类的虚函数表。(所以对象的size比其包含的所有数据size多4,即虚指针大小)➡️动态绑定:(多态的实现原理)通过指针p找到对象c的vptr通过vptr找到classC的vtbl在vtbl中找到第n个虚函数并调用➡️子类调用父类函数隐......
  • 【侯捷C++面向对象笔记】String类
    关键词:动态内存分配,拷贝赋值,new/delete与Complex类最大的差别:有动态分配的内存空间(char*m_data)TipDemo拷贝赋值函数的一般结构拷贝赋值时,检测自我赋值非常重要自己的内存已经释放了,还怎么把自己赋给自己?new时做了什么?1.分配内存2.static_cast为相应类型指针......
  • KY146 魔咒词典C++
    构建一个map,还是查找问题。麻烦点就是要分解输入的过程#include<iostream>#include<string>#include<map>usingnamespacestd;intmain(){stringa,b;map<string,string>m;while(getline(cin,a)){//构建mapb.clear();if(a[0]==......
  • KY27 查找学生信息C++
    用map做查找就行了。#include<iostream>#include<string>#include<map>usingnamespacestd;structnode{stringname;stringx;intage;};typedefstructnodesinfo;intmain(){intn;while(cin>>n){map<......