What happens when calling the destructor of a thread object that has a condition variable waiting?
Asked Answered
B

3

12

I am using a SynchronisedQueue to communicate between threads. I found that destroying the thread object when the attaching thread is waiting on a condition variable would cause the program crash. This can be corrected by calling detach() before the thread destruction. But I am wondering what happens exactly when a thread waiting a conditional variable got terminated. Is there another way to use condition variable to avoid this?

#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>

template <typename Type> class SynchronisedQueue {
 public:
  void Enqueue(Type const & data) {
    std::unique_lock<std::mutex> lock(mutex_);
    queue_.push(data);
    condition_.notify_one();
  }
  Type Dequeue() {
    std::unique_lock<std::mutex> lock(mutex_);
    while (queue_.empty())
      condition_.wait(lock);
    Type result = queue_.front();
    queue_.pop();
    return result; 
  }
 private:
  std::queue<Type> queue_;
  std::mutex mutex_;
  std::condition_variable condition_; 
};

class Worker {
public:
  Worker(SynchronisedQueue<int> * queue) : queue_(queue) {}
  void operator()() {
    queue_->Dequeue();    // <-- The thread waits here.
  }
private:
  SynchronisedQueue<int> * queue_;
};

int main() {
  auto queue = new SynchronisedQueue<int>();
  Worker worker(queue);
  std::thread worker_thread(worker);
  worker_thread.~thread();  // <-- Crashes the program.
  return 0;
}
Byrne answered 21/12, 2012 at 3:45 Comment(0)
E
19

From the C++11 spec:

30.3.1.3 thread destructor [thread.thread.destr] ~thread();

If joinable(), calls std::terminate(). Otherwise, has no effects.

[ Note: Either implicitly detaching or joining a joinable() thread in its destructor could result in difficult to debug correctness (for detach) or performance (for join) bugs encountered only when an exception is raised. Thus the pro grammer must ensure that the destructor is never executed while the thread is still joinable. — end note ]

So calling a thread destructor without first calling join (to wait for it to finish) or detach is guarenteed to immediately call std::terminate and end the program.

Exorcist answered 21/12, 2012 at 4:12 Comment(7)
I tested it. You are right. You can't even let the main thread return if the worker thread is joinable and running. So I guess this answer(#12208184) is wrong, because std::terminate() or ~thread() when thread is joinable and running would cause the program not the thread to terminate.Byrne
@WiSaGaN: that's what that answer actually says, if you read it.Exorcist
OK. I see that "but they terminate every thread". And joinable() is the only criterion to check if you can safely call ~thread(). Even the thread has finished the thread body, one has to call joinable() or detach() first in order to safely destroy the thread object.Byrne
can you elaborate why standard is done this way. why not call join() in the d-torBarela
@Nick: the quoted note indicates why it is done this way.Exorcist
@Barela In C++20 we'll get std::jthread and its destructor: "if joinable() is true, calls request_stop() and then join(); in either case destructs the jthread object".Mongeau
Nice. I just did class like this the other day. this J is lot Java-like but it is what it is ;)Barela
Z
2

The destructor for std::thread will call std::terminate if it is run on a thread if you not have called join() (to wait the thread to finish) or detach() (to detach the thread from the object) on it.

Your code calls the destructor for worker_thread without calling join() or detach() on it, and so std::terminate is called. This is unrelated to the presence of condition variables.

Zebadiah answered 21/12, 2012 at 4:9 Comment(0)
D
-1

You cannot, ever, destroy a resource while something is, or might be, using it. That's really just common sense.

Dairyman answered 21/12, 2012 at 3:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.