This is a separate question but related to the previous question I asked here
I am using an std::thread
in my C++
code to constantly poll for some data & add it to a buffer. I use a C++ lambda
to start the thread like this:
StartMyThread() {
thread_running = true;
the_thread = std::thread { [this] {
while(thread_running) {
GetData();
}
}};
}
thread_running
is an atomic<bool>
declared in class header. Here is my GetData
function:
GetData() {
//Some heavy logic
}
Next I also have a StopMyThread
function where I set thread_running
to false so that it exits out of the while loop in the lambda block
.
StopMyThread() {
thread_running = false;
the_thread.join();
}
As I understand, I can pause & resume the thread using a std::condition_variable
as pointed out here in my earlier question.
But is there a disadvantage if I just use the std::atomic<bool>
thread_running
to execute or not execute the logic in GetData()
like below ?
GetData() {
if (thread_running == false)
return;
//Some heavy logic
}
Will this burn more CPU cycles compared to the approach of using an std::condition_variable
as described here ?
Summing up, in general atomic operations are faster if contention between threads is sufficiently low.
You can pause a thread by making it wait on a std::mutex .
std::condition_variable The condition_variable class is a synchronization primitive used with a std::mutex to block one or more threads until another thread both modifies a shared variable (the condition) and notifies the condition_variable .
std::atomic. Each instantiation and full specialization of the std::atomic template defines an atomic type. If one thread writes to an atomic object while another thread reads from it, the behavior is well-defined (see memory model for details on data races).
A condition variable is useful when you want to conditionally halt another thread or not. So you might have an always-running "worker" thread that waits when it notices it has nothing to do to be running.
The atomic solution requires your UI interaction synchronize with the worker thread, or very complex logic to do it asynchronously.
As a general rule, your UI response thread should never block on non-ready state from worker threads.
struct worker_thread {
worker_thread( std::function<void()> t, bool play = true ):
task(std::move(t)),
execute(play)
{
thread = std::async( std::launch::async, [this]{
work();
});
}
// move is not safe. If you need this movable,
// use unique_ptr<worker_thread>.
worker_thread(worker_thread&& )=delete;
~worker_thread() {
if (!exit) finalize();
wait();
}
void finalize() {
auto l = lock();
exit = true;
cv.notify_one();
}
void pause() {
auto l = lock();
execute = false;
}
void play() {
auto l = lock();
execute = true;
cv.notify_one();
}
void wait() {
Assert(exit);
if (thread)
thread.get();
}
private:
void work() {
while(true) {
bool done = false;
{
auto l = lock();
cv.wait( l, [&]{
return exit || execute;
});
done = exit; // have lock here
}
if (done) break;
task();
}
}
std::unique_lock<std::mutex> lock() {
return std::unique_lock<std::mutex>(m);
}
std::mutex m;
std::condition_variable cv;
bool exit = false;
bool execute = true;
std::function<void()> task;
std::future<void> thread;
};
or somesuch.
This owns a thread. The thread repeatedly runs task so long as it is in play()
mode. If you pause()
the next time task()
finishes, the worker thread stops. If you play()
before the task()
call finishes, it doesn't notice the pause()
.
The only wait is on destruction of worker_thread
, where it automatically informs the worker thread it should exit and it waits for it to finish.
You can manually .wait()
or .finalize()
as well. .finalize()
is async, but if your app is shutting down you can call it early and give the worker thread more time to clean up while the main thread cleans things up elsewhere.
.finalize()
cannot be reversed.
Code not tested.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With