Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Approach of using an std::atomic compared to std::condition_variable wrt pausing & resuming an std::thread in C++

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 ?

like image 586
TheWaterProgrammer Avatar asked Nov 11 '16 17:11

TheWaterProgrammer


People also ask

Is Atomic faster than mutex?

Summing up, in general atomic operations are faster if contention between threads is sufficiently low.

How do you pause an STD thread?

You can pause a thread by making it wait on a std::mutex .

What is std :: Condition_variable?

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 .

What does std :: atomic do?

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).


1 Answers

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.

like image 144
Yakk - Adam Nevraumont Avatar answered Oct 21 '22 05:10

Yakk - Adam Nevraumont