Mr. Boy
Mr. Boy

Reputation: 63728

Wait for either condition A or condition B in a std::thread

I have what seems a fairly simple need, but I'm new to using std::thread and am not sure I understand it right.

My thread's job is to run a loop: wait until an object needs processing, then process it, then wait, ...

I was about to implement this using a condition_variable before I realised that while the thread is sat idly waiting for a new object, it won't notice a stopThread flag has been set.

I effectively want a way to do wait_for_either(new_data,exit_thread) but am unsure how to elegantly implement this. Older code of similar queue function uses Windows API WaitForMultipleObjects but I want to use this as an opportunity to learn the C++11 way.

Upvotes: 0

Views: 514

Answers (3)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275385

Here is a robust data consuming loop with abort options:

while(true) {
  decltype(dataContainer) data;
  {
    std::unique_lock<std::mutex> lock(mutex);
    cond.wait(lock, [] () { return stopThread || !dataContainer.empty(); });
    if (stopThread)
      return; //exit thread
    data = std::move(dataContainer);
  }
  for (auto&& d:data) {
    if (stopThread) return; // abort
    //process data from d
  }
}

stopThread should be atomic or access in the for(:) loop at the bottom needs be guarded with a mutex.

The access to stopThread in the for(:) loop is optional; without it, it won't abort until it finishes working on the bundle of work it picked up.

dataContainer is a bundle of work to do in the form of a std::vector or somesuch. The thread wakes up, grabs all the work to-do, then works on it.

You could also pop one task from dataContainer instead of taking them all. The resulting code is a touch simpler.

To queue data into dataContainer, you have to lock the mutex, put the data in, then notify:

{
  std::unique_lock<std::mutex> lock(mutex);
  dataContainer.push_back(new_data);
}
cond.notify_one();

to shut down:

{
  std::unique_lock<std::mutex> lock(mutex);
  stopThread = true;
}
cond.notify_all();

Note that even if stopThread is atomic, you need to acquire the mutex. There is otherwise a race condition.

Upvotes: 1

rodrigo
rodrigo

Reputation: 98358

When you wait for data to process, you are actually waiting for the condition variable to be signalled. So just signal the condition variable when you want to exit the thread, just as if the stopThread flag were a special data to be processed.

The code might look like:

void thread_func()
{
    std::unique_lock<std::mutex> lock(mutex);
    for (;;)
    {
        cond.wait(lock, [] () { return stopThread || !dataContainer.empty(); });
        if (stopThread)
            return; //exit thread
        //process data from dataContainer
    }
}

To insert data:

{
    std::unique_lock<std::mutex> lock(mutex);
    dataContainer.push_back(new_data);
    cond.notify_all();
}

And then, when you want to stop the thread:

{
    std::unique_lock<std::mutex> lock(mutex);
    stopThread = true;
    cond.notify_all();
}
thread.join(); //not necessary but probably a good idea

Upvotes: 4

David Haim
David Haim

Reputation: 26486

Semi - pseudo code.

std::atomic_bool programRunning;
std::condition_variable cv;
std::mutex mtx;

std::thread loopThread([&]{
   while(programRunning.load()){
       std::unique_lock<std::mutex> lock(mtx);
        if (newDataAvailable){
            //process new data
        } else{
            cv.wait(lock,[&]{ return dataAvailable || !progamRunning.load(); });
        }
   }

});

{
   std::lock_guard<std::mutex> lock(mtx);
   queueMoreData();
   cv.notify_one();
}

//on exit:
programRunning.store(false);
cv.notify_one();

Upvotes: 0

Related Questions