高精度定时器——windows多媒体定时器、linux posix timer,封装使用

周期性地执行一段代码,while死循环+sleep是一种方式,但是精度在10ms以上。

while死循环里如果没有sleep,那么会单独占用1个CPU核(即CPU占用率很高)。

sleep即使设置为1ms(见下方代码),经示波器测试发现周期至少在10ms以上。

Sleep(1);   //Windows api
WaitForSingleObject(hThread, 1); //Windows api
std::this_thread::sleep_for(std::chrono::milliseconds(1));  //c++ api

因为:sleep 1表示暂停它至少1毫秒。即告诉操作系统将线程放入睡眠队列中,一旦过了1毫秒,就应该认为该线程有资格再次执行。但这仍然取决于操作系统是否能调度您的线程,这可能需要另外10ms (或更多,或更少,取决于各种因素)。

高精度定时器可以精确到1ms,以windows多媒体定时器为例。timeSetEvent()产生一个独立的线程,在一定的中断次数后直接调用回调函数,不等待应用程序的消息队列为空(即不依赖消息机制),保证了实时响应。

#include<iostream>

//windows高精度定时器必须引入如下两行
#include<Windows.h>
#pragma comment(lib,"winmm.lib")

LARGE_INTEGER nEndTime, nBeginTime, nFreq;
double time;

void CALLBACK CallBackFunc(UINT uTimerID, UINT uMsg, DWORD_PTR dwUser, DWORD_PTR dw1, DWORD_PTR dw2)
{
    QueryPerformanceCounter(&nEndTime);
    time = (double)(nEndTime.QuadPart - nBeginTime.QuadPart) * 1000 / (double)nFreq.QuadPart;//ms(开始-停止)/频率即为秒数,精确到小数点后6位
    printf("当前时刻(ms):    %f \n\n", time);
}

int main()
{
    QueryPerformanceFrequency(&nFreq);
    QueryPerformanceCounter(&nBeginTime);//获取开始时刻计数值

    timeSetEvent(1, 1, CallBackFunc, (DWORD)NULL, TIME_PERIODIC);    //每1ms触发一次

    Sleep(10000);
    return 0;
}

回调函数的参数含义参考LPTIMECALLBACK function pointer (Windows) | Microsoft Learn

timeSetEvent()的参数含义参考timeSetEvent function (Windows) | Microsoft Learn

为了方便跨平台,封装为1个头文件,使用时引入即可。

HighResolutionTimer.hpp

#ifndef HIGHRESOLUTIONTIMER_H
#define HIGHRESOLUTIONTIMER_H

#if defined(_MSC_VER_) || defined(WIN64) || defined(_WIN64) || defined(__WIN64__) || defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__)
    #ifndef OS_IS_WIN
        #define OS_IS_WIN    
    #endif
#else
    #ifndef OS_IS_LNIX
        #define OS_IS_LNIX    
    #endif
#endif

#if defined (OS_IS_WIN)
    #include <Windows.h>
    #pragma comment(lib,"winmm.lib")
#else
    #include <stdlib.h>
    #include <signal.h>
    #include <time.h>
    #include <sys/time.h>
    #include <unistd.h>
#endif

#include <iostream>
#include <functional>
/// 定时器回调函数
using OnTimeoutFunc = std::function<void()>;    
/// 定时器类型
enum TIMER_TYPE
{
    TT_PERIOD=1,    //周期执行
    TT_ONCE=0        //单次触发
};

class HighResolutionTimer
{
public:
    inline HighResolutionTimer();
    inline ~HighResolutionTimer();
    inline int init(const TIMER_TYPE timer_type, OnTimeoutFunc cb);
    inline int begin(const unsigned int interval_ms);
    inline int end();

private:
    int m_timer_type = TT_ONCE;
    OnTimeoutFunc m_timeout_func = nullptr;

#ifdef OS_IS_WIN
    inline static void CALLBACK TimeProc(UINT uTimerID, UINT uMsg, DWORD_PTR dwUser, DWORD_PTR dw1, DWORD_PTR dw2);
    unsigned int m_timer_id = 0;
    unsigned int m_timer_precision = 1;                //系统所支持的定时器最高精度(ms)
#else
    inline static void handler(int,siginfo_t* si,void*);
    timer_t timerid = nullptr;
    struct sigevent sev;
    struct sigaction sa;
    struct itimerspec its;
#endif
};

HighResolutionTimer::HighResolutionTimer()
{
#ifdef OS_IS_WIN
    TIMECAPS tc;
    if (timeGetDevCaps(&tc, sizeof(TIMECAPS)) != TIMERR_NOERROR)
    {
        printf("call [timeGetDevCaps] function error");
    }
    else
    {
        m_timer_precision = tc.wPeriodMin;            //定时器所支持的最高精度
        timeBeginPeriod(m_timer_precision);
    }
#else
    sa.sa_flags = SA_SIGINFO;
    sa.sa_sigaction = handler;
    sigemptyset(&sa.sa_mask);
    if (sigaction(SIGRTMIN, &sa, NULL) == -1)
        printf("Could not create signal handler");
    sev.sigev_notify = SIGEV_SIGNAL;
    sev.sigev_signo = SIGRTMIN;
    sev.sigev_value.sival_ptr = this;
    if (timer_create(CLOCKID, &sev, &timerid) == -1)
        printf("Could not create timer");
#endif
}

HighResolutionTimer::~HighResolutionTimer()
{
    end();
}

int HighResolutionTimer::init(const TIMER_TYPE timer_type, OnTimeoutFunc cb)
{
    m_timer_type = timer_type;
    m_timeout_func = std::move(cb);

    return 0;
}

int HighResolutionTimer::begin(const unsigned int interval_ms)
{
#ifdef OS_IS_WIN
    end();        //避免多次begin
    if (NULL == (m_timer_id = timeSetEvent(interval_ms, NULL, TimeProc, (DWORD_PTR)this, m_timer_type)))
    {
        printf("failed to create timer");
        return -1;
    }
#else
    switch (m_timer_type)
    {
    case (TT_PERIOD):
        its.it_value.tv_sec = interval_ms / 1000;
        its.it_value.tv_nsec = (interval_ms % 1000) * 1000000;
        its.it_interval.tv_sec = interval_ms / 1000;
        its.it_interval.tv_nsec = (interval_ms % 1000) * 1000000;
        break;
    case (TT_ONCE):
        its.it_value.tv_sec = interval_ms / 1000;
        its.it_value.tv_nsec = (interval_ms % 1000) * 1000000;
        its.it_interval.tv_sec = 0;
        its.it_interval.tv_nsec = 0;
        break;
    default:
        break;
    }

    if (-1 == timer_settime(timerid, 0, &its, NULL))
    {
        printf("call [timer_settime] function error");
        return -1;
    }
#endif

    return 0;
}

int HighResolutionTimer::end()
{
#ifdef OS_IS_WIN
    if (m_timer_id!=0)
    {
        timeKillEvent(m_timer_id);
        m_timer_id = 0;
        timeEndPeriod(m_timer_precision);
    }
#else
    if (timerid)
    {
        struct itimerspec itsnew;
        itsnew.it_value.tv_sec = 0;
        itsnew.it_value.tv_nsec = 0;
        itsnew.it_interval.tv_sec = 0;
        itsnew.it_interval.tv_nsec = 0;
        timer_settime(timerid, 0, &itsnew, &its);
        timer_delete(timerid);
        signal(sev.sigev_signo, SIG_IGN);
        timerid = nullptr;
    }
#endif
    return 0;
}

#ifdef OS_IS_WIN
void CALLBACK HighResolutionTimer::TimeProc(UINT uTimerID, UINT uMsg, DWORD_PTR dwUser, DWORD_PTR dw1, DWORD_PTR dw2)
{
    HighResolutionTimer* pFunc = (HighResolutionTimer*)(dwUser);
    if (pFunc)
    {
        pFunc->m_timeout_func();
    }
}
#else
void HighResolutionTimer::handler(int, siginfo_t* si, void*)
{
    HighResolutionTimer* pFunc = reinterpret_cast<HighResolutionTimer*>(si->si_value.sival_ptr);
    if (pFunc)
    {
        pFunc->m_timeout_func();
    }
}
#endif

#endif

使用

#include"HighResolutionTimer.hpp"

LARGE_INTEGER nEndTime, nBeginTime, nFreq;
double time;

void timeout()
{
    QueryPerformanceCounter(&nEndTime);
    time = (double)(nEndTime.QuadPart - nBeginTime.QuadPart) * 1000 / (double)nFreq.QuadPart;//ms(开始-停止)/频率即为秒数,精确到小数点后6位
    printf("当前时刻(ms):    %f \n\n", time);
}

int main()
{
    QueryPerformanceFrequency(&nFreq);
    QueryPerformanceCounter(&nBeginTime);//获取开始时刻计数值

    HighResolutionTimer hTimer;
    hTimer.init(TT_PERIOD, timeout);    //周期执行
    hTimer.begin(1);                    //周期为1ms

    Sleep(1000000);
    
    return 0;
}

参考c++ 定时器(多媒体定时器, posix定时器) - mohist - 博客园 (cnblogs.com)

posted @ 2024-06-15 15:56  夕西行  阅读(366)  评论(0编辑  收藏  举报