This is a very basic timer that can support multithreading with std::thread
and std::chrono
.
The timer has the classic functions: start()
and stop()
.
The start()
method creates an independent thread (if multithread support is enabled), then sleep the thread for a given Interval
, then execute Timeout
function. This method sets the running
flag to true.
If singleShot
flag is not enabled the sleepThenTimeout
process is called while a running
flag is true
.
If multithread support is not enabled, the current
thread is sleep.
The stop()
method just sets the running
flag to false and join
the thread.
My doubt is about threat-safety. I've just started to learn how multithreading works, so I'm not sure if I must use mutexes or something like that.
Any other type of feedback are welcome!
Timer.h
#ifndef TIMER_H
#define TIMER_H
#include <thread>
#include <chrono>
class Timer
{
public:
typedef std::chrono::milliseconds Interval;
typedef std::function<void(void)> Timeout;
Timer(const Timeout &timeout);
Timer(const Timeout &timeout,
const Interval &interval,
bool singleShot = true);
void start(bool multiThread = false);
void stop();
bool running() const;
void setSingleShot(bool singleShot);
bool isSingleShot() const;
void setInterval(const Interval &interval);
const Interval &interval() const;
void setTimeout(const Timeout &timeout);
const Timeout &timeout() const;
private:
std::thread _thread;
bool _running = false;
bool _isSingleShot = true;
Interval _interval = Interval(0);
Timeout _timeout = nullptr;
void _temporize();
void _sleepThenTimeout();
};
#endif // TIMER_H
Timer.cpp
#include "Timer.h"
Timer::Timer(const Timeout &timeout)
: _timeout(timeout)
{
}
Timer::Timer(const Timer::Timeout &timeout,
const Timer::Interval &interval,
bool singleShot)
: _isSingleShot(singleShot),
_interval(interval),
_timeout(timeout)
{
}
void Timer::start(bool multiThread)
{
if (this->running() == true)
return;
_running = true;
if (multiThread == true) {
_thread = std::thread(
&Timer::_temporize, this);
}
else{
this->_temporize();
}
}
void Timer::stop()
{
_running = false;
_thread.join();
}
bool Timer::running() const
{
return _running;
}
void Timer::setSingleShot(bool singleShot)
{
if (this->running() == true)
return;
_isSingleShot = singleShot;
}
bool Timer::isSingleShot() const
{
return _isSingleShot;
}
void Timer::setInterval(const Timer::Interval &interval)
{
if (this->running() == true)
return;
_interval = interval;
}
const Timer::Interval &Timer::interval() const
{
return _interval;
}
void Timer::setTimeout(const Timeout &timeout)
{
if (this->running() == true)
return;
_timeout = timeout;
}
const Timer::Timeout &Timer::timeout() const
{
return _timeout;
}
void Timer::_temporize()
{
if (_isSingleShot == true) {
this->_sleepThenTimeout();
}
else {
while (this->running() == true) {
this->_sleepThenTimeout();
}
}
}
void Timer::_sleepThenTimeout()
{
std::this_thread::sleep_for(_interval);
if (this->running() == true)
this->timeout()();
}
main.cpp
#include <iostream>
#include "Timer.h"
using namespace std;
int main(void)
{
Timer tHello([]()
{
cout << "Hello!" << endl;
});
tHello.setSingleShot(false);
tHello.setInterval(Timer::Interval(1000));
tHello.start(true);
Timer tStop([&]()
{
tHello.stop();
});
tStop.setSingleShot(true);
tStop.setInterval(Timer::Interval(3000));
tStop.start();
return 0;
}
-
\$\begingroup\$ Why would you use up all the resources required for a thread when you can set SIGALARM? \$\endgroup\$Loki Astari– Loki Astari2014年02月05日 05:00:14 +00:00Commented Feb 5, 2014 at 5:00
-
\$\begingroup\$ As far I know SIGALRM is not cross-plataform. \$\endgroup\$Mario Frescas– Mario Frescas2014年02月06日 03:20:52 +00:00Commented Feb 6, 2014 at 3:20
-
\$\begingroup\$ What platform are you thinking about that does not have sig alarm but does have a threading model? \$\endgroup\$Loki Astari– Loki Astari2014年02月06日 05:34:26 +00:00Commented Feb 6, 2014 at 5:34
-
\$\begingroup\$ Sorry, my bad! I've chosen to use threads because I want to learn about threads. I've also could use Qt's Timer but that is not the way. \$\endgroup\$Mario Frescas– Mario Frescas2014年02月09日 03:55:23 +00:00Commented Feb 9, 2014 at 3:55
2 Answers 2
Basically you are working with these class members inside the thread functions _temporize
and _sleepThenTimeout
.
timeout
,_isSingleShort
and_interval
, these three cannot be changed after thestart
function is called, so it is safe to use them._running
on the other hand can be read/write by both threads. In reality it might not cause any problem as assignment tobool
is atomic (on most architectures), but to be 100% safe you can usestd::atomic<bool>
.
Another important thing to keep in mind is that while timer class itself is thread safe, it is responsibility of the user of this class to make sure that the timer call back function (_timeout
) is thread-safe by it self, as it will be executed in a separate thread.
-
\$\begingroup\$ What do you think about replace
if (this->running() == true) return;
with an assert? \$\endgroup\$Mario Frescas– Mario Frescas2014年02月08日 23:32:57 +00:00Commented Feb 8, 2014 at 23:32 -
1\$\begingroup\$ Yes, assert will be better suited here, as it is better to let the caller know of the problem, rather than failing silently. But
assert
or theif
statement won't affect the thread safety. \$\endgroup\$Ammar– Ammar2014年02月09日 08:08:33 +00:00Commented Feb 9, 2014 at 8:08
I changed the testing part in main to
int cntHello = 0;
int cntStop = 0;
Timer tHello([&]()
{
cntHello++;
});
tHello.setSingleShot(false);
tHello.setInterval(Timer::Interval(1000));
tHello.start(true);
Timer tStop([&]()
{
++cntStop;
tHello.stop();
});
tStop.setSingleShot(true);
tStop.setInterval(Timer::Interval(3001));
tStop.start(true);
// TODO why could join tHello at this point?
// tHello.join();
tStop.join();
tHello.join();
First change is the during creating the Timer for tHello. I add '&' between '[]' otherwise I cout not use cntHello inside the function. Why? (The counters are used later for checking how often the functions are called.)
Second I use tStop allthough as multithread and wait after starting it until the threads have been finshed.(by using join) For that I wrote methods join() and joinable() for the class Timer with only the the methods for _thread. This works if I join for tStop first. If I join tHello first I got an exception. Can you explain why?