C++中的chrono使用及实现异步定时器timer
由于C++标准中没有定时器,本文使用C++11相关语法并进行以下封装
大致流程为定时器启动时创建两个线程分别用于时间处理和函数回调操作;
m_timerThread 每个定时器周期都会向queue中写入sn并触发条件变量的notify,sn每次notify时会自动+1,除非该定时器执行了stop或定时器重新start了;
m_workerThread 会在条件变量被notify后读取queue中的sn,如果sn符合条件则执行callback,除非该定时器执行了stop或callback无效;
设计两个线程的目的是为了尽可能的避免单线程工作中callback处理时间的不确定性影响到定时器触发的波动;
另外start中对sleep时间进行了一定的校正,即在原来基础上 -0.5ms这是考虑到sleep之外的其他动作可能导致的耗时而做的简单修正;
本封装对外提供接口start和stop,并禁止定时器拷贝和赋值;
调用start时会自动结束掉正在运行的timer(如果timer没有stop)并重新开始使用最新的参数来计时和触发新的callback;
可以使用的接口原型如下
void start(int intervalmillis, std::function<void()> callback);
void stop();
//mytimer.hpp
#ifndef __SWTIMER_H__
#define __SWTIMER_H__
#include <iostream>
#include <queue>
#include <functional>
#include <chrono>
#include <thread>
#include <atomic>
#include <memory>
#include <mutex>
#include <condition_variable>
#if 0
extern long long getSteadyMillis();
#define DEBUG_TIMER_BEGIN std::cout<<__func__<<" begin:"<<getSteadyMillis()<<std::endl;
#define DEBUG_TIMER_END std::cout<<__func__<<" end:"<<getSteadyMillis()<<std::endl;
#else
#define DEBUG_TIMER_BEGIN
#define DEBUG_TIMER_END
#endif
class SWTimer {
public:
SWTimer():m_timerMainThreadAlive(false) {
m_timerStarted = false;
m_callbackSn = (uint32_t)-1;
m_sleepMicros = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::milliseconds(1000));
m_callbackFunc = std::function<void()>(nullptr);
create();
}
~SWTimer() {
destroy();
// std::cout << "~cpptimer" << std::endl;
}
SWTimer(const SWTimer&) = delete;
SWTimer &operator=(const SWTimer&) = delete;
private:
void create() {
DEBUG_TIMER_BEGIN
if (m_timerMainThreadAlive) {
return;
}
m_timerMainThreadAlive = true;
m_timerThread = std::thread([this]() { //lambda
do {
if (timerWait()) {
doNotify(); // notify work thread
}
} while (m_timerMainThreadAlive);
});
m_workerThread = std::thread([this]() {
do {
workWait(); // wait for timer notify
doCallback();
} while (m_timerMainThreadAlive);
});
DEBUG_TIMER_END
}
void destroy() {
m_timerMainThreadAlive = false;
stop();
m_workerCond.notify_all();
m_timerCond.notify_all();
if (m_timerThread.joinable()) {
m_timerThread.join();
}
if (m_workerThread.joinable()) {
m_workerThread.join();
}
}
public:
void start(int intervalmillis, std::function<void()> callback) {
//callback: std::bind(funcname, parameters...);
std::chrono::milliseconds interval_millis(intervalmillis);
std::chrono::microseconds interval_micros = std::chrono::duration_cast<std::chrono::microseconds>(interval_millis);
std::chrono::microseconds adjust_micros(500);
std::chrono::microseconds sleep_micros(interval_micros - adjust_micros);
DEBUG_TIMER_BEGIN
{
MuxGuard g(m_MuxLock);
while (!m_workQueue.empty()) {
m_workQueue.pop();
}
m_timerStarted = true;
m_callbackSn = 0;
m_callbackFunc = callback;
m_sleepMicros = sleep_micros;
m_timerQueue.push(m_callbackSn);
if (!m_timerMainThreadAlive) {
create();
}
}
m_timerCond.notify_one();
DEBUG_TIMER_END
}
bool timerWait() {
DEBUG_TIMER_BEGIN
MuxUniqLck tul(m_MuxLock);
uint32_t sn = 0;
if (!m_timerQueue.empty()) {
while (!m_timerQueue.empty()) {
m_timerQueue.pop();
}
// std::cout<<"return false queue, "<<getSteadyMillis()<<std::endl;
return false;
}
sn = m_callbackSn;
m_startTimePoint = std::chrono::steady_clock::now();
m_timerCond.wait_for(tul, std::chrono::microseconds(m_sleepMicros));
if (!m_timerStarted) {
// std::cout<<"return false not started, "<<getSteadyMillis()<<std::endl;
return false;
}
if (sn != m_callbackSn) {
// std::cout<<"return false sn!=, "<<getSteadyMillis()<<std::endl;
return false;
}
m_nowTimePoint = std::chrono::steady_clock::now();
m_deltaMicros = std::chrono::duration_cast<DurationMicrosT>(m_nowTimePoint-m_startTimePoint);
// std::cout<<"delta:"<<m_deltaMicros.count()<<",sleep:"<<m_sleepMicros.count()<<std::endl;
if (m_deltaMicros.count() < m_sleepMicros.count()/2) {
// std::cout<<"return false interval, "<<getSteadyMillis()<<std::endl;
return false;
}
DEBUG_TIMER_END
// std::cout<<"return true: "<<getSteadyMillis()<<std::endl;
m_timerQueue.push(sn);
return true;
}
void doNotify() {
{
MuxGuard g(m_MuxLock);
uint32_t sn = 0;
if (m_timerQueue.empty()) {
return;
}
sn = m_timerQueue.front();
m_timerQueue.pop();
if (sn != m_callbackSn) {
return;
}
if (!m_timerStarted) {
return;
}
m_callbackSn += 1;
m_workQueue.push(m_callbackSn);
}
m_workerCond.notify_one();
}
void workWait() {
MuxUniqLck wul(m_MuxLock);
while (m_workQueue.empty() && m_timerMainThreadAlive) {
m_workerCond.wait(wul);
}
}
void doCallback() {
DEBUG_TIMER_BEGIN
std::function<void()> callback = std::function<void()>(nullptr);
{
MuxGuard g(m_MuxLock);
uint32_t sn = 0;
while (!m_workQueue.empty()) {
sn = m_workQueue.front();
m_workQueue.pop();
};
// std::cout<<"sn:"<<sn<<",m_callbackSn="<<m_callbackSn<<std::endl;
if (sn == 0) {
return;
}
if (!m_timerStarted) {
return;
}
if (sn != m_callbackSn) {
return;
}
// std::cout<<"m_callbackFunc:"<<(m_callbackFunc?"callable":"not callable")<<std::endl;
if (!m_callbackFunc) {
return;
}
callback = m_callbackFunc;
}
// release lock when do callback call, prevent callback spend too much time
try {
callback();
} catch (const std::bad_function_call &e) {
(void)e;
// std::cout<<__func__<<" error: "<<e.what()<<std::endl;
}
DEBUG_TIMER_END
}
void stop() {
DEBUG_TIMER_BEGIN
{
MuxGuard g(m_MuxLock);
while (!m_workQueue.empty()) {
m_workQueue.pop();
}
m_timerStarted = false;
m_callbackSn = (uint32_t)-1;
m_callbackFunc = std::function<void()>(nullptr);
}
DEBUG_TIMER_END
}
private:
typedef std::chrono::duration<int, std::ratio<1, 1000000>> DurationMicrosT;
mutable std::mutex m_MuxLock;
using MuxGuard = std::lock_guard<std::mutex>;
using MuxUniqLck = std::unique_lock<std::mutex>;
std::condition_variable m_timerCond;
std::condition_variable m_workerCond;
std::thread m_timerThread;
std::thread m_workerThread;
std::atomic<bool> m_timerMainThreadAlive;
std::atomic<bool> m_timerStarted;
std::atomic<uint32_t> m_callbackSn;
std::function<void()> m_callbackFunc = std::function<void()>(nullptr);
std::chrono::microseconds m_sleepMicros;
std::queue<uint32_t> m_workQueue;
std::queue<uint32_t> m_timerQueue;
std::chrono::steady_clock::time_point m_startTimePoint;
std::chrono::steady_clock::time_point m_nowTimePoint;
DurationMicrosT m_deltaMicros;
};
#endif //__SWTIMER_H__
测试demo代码
//mytimer.cpp
/*****************************************
* Copyright (C) 2020 * Ltd. All rights reserved.
* File name : mytimer.cpp
* Created date: 2020-05-07 00:35:00
*******************************************/
#include <iostream>
#include <string>
#include <memory>
#include <time.h>
#include "mytimer.hpp"
using namespace std;
long long g_totalCnt = 0;
long long g_starttime = 0;
long long g_lasttime = 0;
long long g_warncount = 0;
long long getSteadyMillis()
{
//CLOCK_REALTIME:系统相对时间,从UTC 1970-1-1 0:0:0开始计时,更改系统时间会更改获取的值;
//CLOCK_MONOTONIC:系统绝对时间/单调时间,为系统重启到现在的时间,更改系统时间对它没有影响;
//CLOCK_PROCESS_CPUTIME_ID:本进程到当前代码系统CPU花费的时间;
//CLOCK_THREAD_CPUTIME_ID:本线程到当前代码系统CPU花费的时间;
struct timespec ts {};
(void)clock_gettime(CLOCK_MONOTONIC, &ts);
long long milliseconds = (ts.tv_sec*1000) + (ts.tv_nsec/1000000);
return milliseconds;
}
long long getSystemMillis()
{
struct timespec ts {};
(void)clock_gettime(CLOCK_REALTIME, &ts);
long long milliseconds = (ts.tv_sec*1000) + (ts.tv_nsec/1000000);
return milliseconds;
}
void timerCallbackFunc(std::string &&info, int number) {
g_totalCnt += 1;
long long millis = getSteadyMillis();
if (g_starttime == 0) {
g_starttime = millis;
}
if (g_lasttime == 0) {
g_lasttime = millis;
}
long long interval = millis - g_lasttime;
long long spent = millis - g_starttime;
if (interval < 50) {
std::cout<<"###### "<<__func__<<" warning: unexpected interval="<<interval<<std::endl;
g_warncount += 1;
}
std::cout<<__func__<<":sn="<<g_totalCnt<<",warn="<<g_warncount
<<",sys="<<getSystemMillis()<<",now="<<millis<<",spent="<<spent
<<",iv="<<interval<<",info="<<info<<",number="<<number<<std::endl;
g_lasttime = millis;
}
int main() {
SWTimer *ptimer = new SWTimer();
if (ptimer == NULL) {
std::cout << "ERROR: create timer failed." << std::endl;
return -1;
}
// std::this_thread::sleep_for(std::chrono::seconds(3));
//周期性执行定时任务
g_starttime = getSteadyMillis();
g_lasttime = getSteadyMillis();
ptimer->start(100, std::bind(timerCallbackFunc,"periodic timer!", 1));
std::this_thread::sleep_for(std::chrono::seconds(3));
ptimer->stop();
int i = 0;
std::string info;
for (i = 0; i< 20000; i++) {
info = "hi";
g_lasttime = getSteadyMillis();
ptimer->start(100, std::bind(timerCallbackFunc, info.c_str(), i));
std::this_thread::sleep_for(std::chrono::seconds(1));
ptimer->stop();
}
g_lasttime = getSteadyMillis();
ptimer->start(1000, std::bind(timerCallbackFunc,"periodic timer!", 1));
std::this_thread::sleep_for(std::chrono::seconds(6));
ptimer->stop();
std::this_thread::sleep_for(std::chrono::seconds(3));
if (ptimer != NULL) {
delete ptimer;
ptimer = NULL;
}
time_t now = time(0);
std::cout<<"at "<<asctime(localtime(&now))<<std::endl;
std::cout<<"warning time="<<g_warncount<<std::endl;
return 0;
}
验证log
[~/tmp]$ g++ -std=c++11 cpptimer.cpp -o cpptimer -lpthread
[~/tmp]$ ./cpptimer
start begin:95239177059
start end:95239177065
timerCallbackFunc:sn=31,warn=0,now=95239277011,spendtime=3100322,interval=99954,info=hi,number=0
timerCallbackFunc:sn=32,warn=0,now=95239377039,spendtime=3200350,interval=100028,info=hi,number=0
timerCallbackFunc:sn=33,warn=0,now=95239477393,spendtime=3300704,interval=100354,info=hi,number=0
timerCallbackFunc:sn=34,warn=0,now=95239577355,spendtime=3400666,interval=99962,info=hi,number=0
timerCallbackFunc:sn=35,warn=0,now=95239677056,spendtime=3500367,interval=99701,info=hi,number=0
timerCallbackFunc:sn=36,warn=0,now=95239776947,spendtime=3600258,interval=99891,info=hi,number=0
timerCallbackFunc:sn=37,warn=0,now=95239877095,spendtime=3700406,interval=100148,info=hi,number=0
timerCallbackFunc:sn=38,warn=0,now=95239976958,spendtime=3800269,interval=99863,info=hi,number=0
timerCallbackFunc:sn=39,warn=0,now=95240077673,spendtime=3900984,interval=100715,info=hi,number=0
stop begin:95240177535
stop end:95240177590
start begin:95240177598
start end:95240177601
timerCallbackFunc:sn=40,warn=0,now=95240277615,spendtime=4100926,interval=100020,info=hi,number=1
timerCallbackFunc:sn=41,warn=0,now=95240378106,spendtime=4201417,interval=100491,info=hi,number=1
timerCallbackFunc:sn=42,warn=0,now=95240478362,spendtime=4301673,interval=100256,info=hi,number=1
timerCallbackFunc:sn=43,warn=0,now=95240578363,spendtime=4401674,interval=100001,info=hi,number=1
timerCallbackFunc:sn=44,warn=0,now=95240679124,spendtime=4502435,interval=100761,info=hi,number=1
timerCallbackFunc:sn=45,warn=0,now=95240780002,spendtime=4603313,interval=100878,info=hi,number=1
timerCallbackFunc:sn=46,warn=0,now=95240880534,spendtime=4703845,interval=100532,info=hi,number=1
timerCallbackFunc:sn=47,warn=0,now=95240980273,spendtime=4803584,interval=99739,info=hi,number=1
timerCallbackFunc:sn=48,warn=0,now=95241080796,spendtime=4904107,interval=100523,info=hi,number=1
stop begin:95241178385
stop end:95241178442