Skip to content

Instantly share code, notes, and snippets.

@vtellier
Created November 30, 2016 12:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vtellier/6664547a3f9372de3ba6d2f3f264c922 to your computer and use it in GitHub Desktop.
Save vtellier/6664547a3f9372de3ba6d2f3f264c922 to your computer and use it in GitHub Desktop.
Deterministic, re-settable, stoppable C++11 timer
#pragma once
#include <exception>
#include <system_error>
#include <thread>
#include <condition_variable>
#include <mutex>
#include <functional>
#include <chrono>
#include <memory>
using namespace std;
using namespace chrono;
class Timer
{
public:
typedef shared_ptr<Timer> Ptr;
typedef function<void(Timer*)> Callback;
const static long long MinPeriod = 5;
const milliseconds period;
static Ptr Create(Callback callback, long long period, bool autoStop = false)
{
return Ptr(new Timer(callback, period, autoStop));
}
~Timer()
{
Stop();
if(_thread.joinable())
{
try
{
_thread.join();
}
catch(const std::system_error& e)
{
// TODO use a standard logger.
cout << "Timer:: error: " << e.what() << endl;
throw e;
}
}
}
void Start(bool reset = false)
{
// To reset, we must first stop it.
if(reset)
Stop();
// When the timer has been stoppped, we must be able to restart it later.
if(_stopCommand && _thread.joinable())
{
_thread.join();
}
// The actual start operation
{
lock_guard<mutex> guard(_startStopMutex);
if(!_thread.joinable())
{
unique_lock<mutex> lk(_stopMutex);
_stopCommand = false;
lk.unlock();
_stopCondition.notify_all();
// We set the time of the first iteration
_nextCall = high_resolution_clock::now();
// And we create the thread.
_thread = thread(&Timer::loop, this);
}
}
}
void Stop()
{
lock_guard<mutex> guard(_startStopMutex);
if(_thread.joinable())
{
unique_lock<mutex> lk(_stopMutex);
_stopCommand = true;
lk.unlock();
_stopCondition.notify_all();
}
}
private:
time_point<high_resolution_clock> _nextCall;
Callback _callback;
thread _thread;
mutex _startStopMutex;
condition_variable _stopCondition;
mutex _stopMutex;
bool _stopCommand;
bool _autoStop;
Timer(Callback callback, long long period, bool autoStop) :
period(milliseconds{ period }),
_nextCall(high_resolution_clock::now()),
_callback(callback),
_stopCondition(),
_stopMutex(),
_stopCommand(false),
_autoStop(autoStop),
_thread()
{
if(period < MinPeriod)
throw std::exception("Timer does not support a period lower or equal than 5 milliseconds.");
}
void loop()
{
bool stop = false;
while(!stop)
{
// By doing so we ensure the call to be deterministic.
do
{
_nextCall += period;
//cout << "loop() +period" << endl;
}
while(_nextCall < high_resolution_clock::now());
// We scope here because the user's call might call the stop method, and so create a deadlock.
{
// We wait for a stop command with a timeout of 'period'
unique_lock<mutex> lk(_stopMutex);
if(_stopCondition.wait_until(lk, _nextCall, [&]{return _stopCommand;}))
{
// mutex has been unlocked: this is the stop command
stop = true;
}
}
// If call to Stop in callback lead to deadlcok then uncomment this code.
if(!stop)
{
// Call user's code
_callback(this);
}
// We call the Stop method because it is a normal exit, and the Stop function ensures
// that we can restart the timer using the Start method.
if(_autoStop)
Stop();
}
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment