What happens when calling the destructor of a thread object that has a condition variable waiting?
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.
Destruction of condition variable randomly loses notification
I am pretty sure your vendors implementation is broken. Your program looks almost OK from the perspective of obeying the contract with the cv/mutex classes. I couldn’t 100% verify, I am behind one version.
The notion of “blocking” is confusing in the condition_variable (CV) class because there are multiple things to be blocking on. The contract requires the implementation to be more complex than a veneer on pthread_cond* (for example). My reading of it indicates that a single CV would require at least 2 pthread_cond_t’s to implement.
The crux is the destructor having a definition while threads are waiting upon a CV; and its ruin is in a race between CV.wait and ~CV. The naive implementation simply has ~CV broadcast the condvar then eliminate it, and has CV.wait remember the lock in a local variable, so that when it awakens from the runtime notion of blocking it no longer has to reference the object. In that implementation, ~CV becomes a “fire and forget” mechanism.
Sadly, a racing CV.wait could meet the preconditions, yet not be finished interacting with the object yet, when ~CV sneaks in and destroys it. To resolve the race CV.wait and ~CV need to exclude each other, thus the CV requires at least a private mutex to resolve races.
We aren’t finished yet. There usually isn’t an underlying support [ eg. kernel ] for an operation like “wait on cv controlled by lock and release this other lock once I am blocked”. I think that even the posix folks found that too funny to require. Thus, burying a mutex in my CV isn’t enough, I actually require a mechanism that permits me to process events within it; thus a private condvar is required inside the implementation of CV. Obligatory David Parnas meme.
Almost OK, because as Marek R points out, you are relying on referencing a class after its destruction has begun; not the cv/mutex class, your notify_on_delete class. The conflict is a bit academic. I doubt clang would depend upon nod remaining valid after control had transferred to nod->cv.wait(); but the real customer of most compiler vendors are benchmarks, not programmers.
As as general note, multi-threaded programming is difficult, and having now peaked at the c++ threading model, it might be best to give it a decade or two to settle down. It’s contracts are astonishing. When I first looked at your program, I thought ‘duh, there is no way you can destroy a cv that can be accessed because RAII’. Silly me.
Pthreads is another awful API for threading. At least it doesn’t attempt over-reach, and is mature enough that robust test suites keep vendors in line.
Destruction of class with condition variable in wait
Suppose we are calling ~myclass
from the master thread (call it Thread A), you can check whether it is needed to be stopped in the slave thread (call it Thread B):
And we have an Atomic<bool>
variable named Stop
which is accessible from both Threads.
In the thread A:
~myclass()
{
Stop = true;
myConditionVariable.notifyAll();
myThread.join();
}
In the thread B:
myConditionVariable.wait(myMutexLock, [&] return Stop || haveWorkToDo(); );
if (Stop)
// Terminate Thread
// continue to work
Destroying condition_variable while waiting
You can delete it right away.
Quote from C++ standard:
~ condition_variable();
Requires: There shall be no thread blocked on
*this
. [Note: That is,
all threads shall have been notified; they may subsequently block on
the lock specified in thewait
. This relaxes the usual rules, which
would have required allwait
calls to happen before destruction. Only
the notification to unblock thewait
must happen before destruction.
Basically wait
functions are required to perform locking and waiting atomically:
The execution of
notify_one
andnotify_all
shall be atomic. The execution
ofwait
,wait_for
, andwait_until
shall be performed in three atomic parts:
- the release of the mutex and entry into the waiting state;
- the unblocking of the
wait
; and- the reacquisition of the lock.
Once notify
wakes a thread, it should be considered "unblocked" and should contest the mutex.
There are similar guarantees about std::mutex
: threads are not required to leave unlock
before mutex is destroyed.
Quote from C++ standard:
The implementation shall provide
lock
andunlock
operations, as
described below. For purposes of determining the existence of a data
race, these behave as atomic operations. Thelock
andunlock
operations on a single mutex shall appear to occur in a single total
order.
Later:
Note: After a thread A has called
unlock()
, releasing a mutex, it is
possible for another thread B to lock the same mutex, observe that it
is no longer in use, unlock it, and destroy it, before thread A
appears to have returned from itsunlock
call.
Such guarantees are required to avoid issues like this, when mutex inside an object is used to protect object reference counter.
Note that this does not guarantee that your implementation has no bugs in this regard. In the past glibc had multiple bugs related to the destruction of synchronization objects, in particular pthread_mutex_unlock
was accessing mutex before returning.
Destructor, when object's dynamic variable is locked by mutex will not free it?
- Is is it possible that var will not be deleted in destructor?
With
~Object()
{
mu.lock();
delete[]var; // destructor should free all dynamic memory on it's own, as I remember
mu.unlock();
}
You might have to wait that lock
finish, but var
would be deleted.
Except that your program exhibits undefined behaviour with non protected concurrent access to object
. (delete object
isn't protected, and you read it in your another thread), so everything can happen.
- Do I use mutex with detached threads correctly in code above?
Detached or not is irrelevant.
And your usage of mutex is wrong/incomplete.
which variable should your mutex be protecting?
It seems to be a mix between object
and var
.
If it is var
, you might reduce scope in do_something
(lock only in if-block)
And it misses currently some protection to object
.
2.1 Do I need cover by
mutex::lock
andmutex::unlock
alsodelete object
line?
Yes object
need protection.
But you cannot use that same mutex for that. std::mutex
doesn't allow to lock twice in same thread (a protected delete[]var;
inside a protected delete object
) (std::recursive_mutex
allows that).
So we come back to the question which variable does the mutex protect?
if only object
(which is enough in your sample), it would be something like:
#include <thread>
#include <mutex>
using namespace std;
mutex mu;
class Object
{
public:
char *var;
Object()
{
var = new char[1]; var[0] = 1;
}
~Object()
{
delete[]var; // destructor should free all dynamic memory on it's own, as I remember
}
}*object = nullptr;
void do_something()
{
for(;;)
{
mu.lock();
if(object)
if(object->var[0] < 255)
object->var[0]++;
else
object->var[0] = 0;
mu.unlock();
}
}
int main()
{
object = new Object();
thread th(do_something);
th.detach();
Sleep(1000);
mu.lock(); // or const std::lock_guard<std::mutex> lock(mu); and get rid of unlock
delete object;
object = nullptr;
mu.unlock();
return 0;
}
Alternatively, as you don't have to share data between thread, you might do:
int main()
{
Object object;
thread th(do_something);
Sleep(1000);
th.join();
return 0;
}
and get rid of all mutex
How to reattach thread or wait for thread completion before exiting
Assuming that the IO thread is coded by you, you can handle this with a combination of std::promise
and std::future
, something like this:
#include <chrono>
#include <thread>
#include <future>
#include <iostream>
using namespace std::chrono_literals;
void demo_thread (std::promise <bool> *p)
{
std::cout << "demo thread waiting...\n";
std::this_thread::sleep_for (1000ms);
std::cout << "demo thread terminating\n";
p->set_value (true);
}
int main ()
{
std::promise <bool> p;
std::thread t = std::thread (demo_thread, &p);
t.detach ();
// ...
std::cout << "main thread waiting...\n";
std::future <bool> f = p.get_future();
f.wait ();
std::cout << "main thread terminating\n";
}
Live demo
Is it a good idea to shut down a class's thread member in the class's destructor?
It is a good idea to release resources a class creates when the class is destroyed, even if one of the resources is a thread. If the resource is created explicitly via a user call, such as Worker::Start()
, then there should also be an explicit way to release it, such as Worker::Stop()
. It would also be a good idea to either perform cleanup in the destructor in the event that the user does not call Worker::Stop()
and/or provide the user a scoped helper class that implements the RAII-idiom, invoking Worker::Start()
in its constructor and Worker::Stop()
in its destructor. However, if resource allocation is done implicitly, such as in the Worker
constructor, then the release of the resource should also be implicit, leaving the destructor as the prime candidate for this responsibility.
Destruction
Lets examine Worker::~Worker()
. A general rule is to not throw exceptions in destructors. If a Worker
object is on a stack that is unwinding from another exception, and Worker::~Worker()
throws an exception, then std::terminate()
will be invoked, killing the application. While Worker::~Worker()
is not explicitly throwing an exception, it is important to consider that some of the functions it is invoking may throw:
m_Condition.notify_one()
does not throw.m_pThread->join()
could throwboost::thread_interrupted
.
If std::terminate()
is the desired behavior, then no change is required. However, if std::terminate()
is not desired, then catch boost::thread_interrupted
and suppress it.
Worker::~Worker()
{
m_Running = false;
m_Condition.notify_one();
try
{
m_pThread->join();
}
catch ( const boost::thread_interrupted& )
{
/* suppressed */
}
}
Concurrency
Managing threading can be tricky. It is important to define the exact desired behavior of functions like Worker::Wake()
, as well as understand the behavior of the types that facilitate threading and synchronization. For example, boost::condition_variable::notify_one()
has no effect if no threads are blocked in boost::condition_variable::wait()
. Lets examine the possible concurrent paths for Worker::Wake()
.
Below is a crude attempt to diagram concurrency for two scenarios:
- Order-of-operation occurs from top-to-bottom. (i.e. Operations at the top occur before operations at the bottom.
- Concurrent operations are written on the same line.
<
and>
are used to highlight when one thread is waking up or unblocking another thread. For exampleA > B
indicates that threadA
is unblocking threadB
.
Scenario: Worker::Wake()
invoked while Worker::ThreadProc()
is blocked on m_Condition
.
Other Thread | Worker::ThreadProc
-----------------------------------+------------------------------------------
| lock( m_Mutex )
| `-- m_Mutex.lock()
| m_Condition::wait( lock )
| |-- m_Mutex.unlock()
| |-- waits on notification
Worker::Wake() | |
|-- lock( m_Mutex ) | |
| `-- m_Mutex.lock() | |
|-- m_Condition::notify_one() > |-- wakes up from notification
`-- ~lock() | `-- m_Mutex.lock() // blocks
`-- m_Mutex.unlock() > `-- // acquires lock
| // do some work here
| ~lock() // end of for loop's scope
| `-- m_Mutex.unlock()
Result: Worker::Wake()
returns fairly quickly, and Worker::ThreadProc
runs.
Scenario: Worker::Wake()
invoked while Worker::ThreadProc()
is not blocked on m_Condition
.
Other Thread | Worker::ThreadProc
-----------------------------------+------------------------------------------
| lock( m_Mutex )
| `-- m_Mutex.lock()
| m_Condition::wait( lock )
| |-- m_Mutex.unlock()
Worker::Wake() > |-- wakes up
| `-- m_Mutex.lock()
Worker::Wake() | // do some work here
|-- lock( m_Mutex ) | // still doing work...
| |-- m_Mutex.lock() // block | // hope we do not block on a system call
| | | // and more work...
| | | ~lock() // end of for loop's scope
| |-- // still blocked < `-- m_Mutex.unlock()
| `-- // acquires lock | lock( m_Mutex ) // next 'for' iteration.
|-- m_Condition::notify_one() | `-- m_Mutex.lock() // blocked
`-- ~lock() | |-- // still blocked
`-- m_Mutex.unlock() > `-- // acquires lock
| m_Condition::wait( lock )
| |-- m_Mutex.unlock()
| `-- waits on notification
| `-- still waiting...
Result: Worker::Wake()
blocked as Worker::ThreadProc
did work, but was a no-op, as it sent a notification to m_Condition
when no one was waiting on it.
This is not particularly dangerous for Worker::Wake()
, but it can cause problems in Worker::~Worker()
. If Worker::~Worker()
runs while Worker::ThreadProc
is doing work, then Worker::~Worker()
may block indefinitely when joining the thread, as the thread may not be waiting on m_Condition
at the point in which it is notified, and Worker::ThreadProc
only checks m_Running
after it is done waiting on m_Condition
.
Working Towards a Solution
In this example, lets define the following requirements:
Worker::~Worker()
will not causestd::terminate()
to be invoked.Worker::Wake()
will not block whileWorker::ThreadProc
is doing work.- If
Worker::Wake()
is called whileWorker::ThreadProc
is not doing work, then it will notifyWorker::ThreadProc
to do work. - If
Worker::Wake()
is called whileWorker::ThreadProc
is doing work, then it will notifyWorker::ThreadProc
to perform another iteration of work. - Multiple calls to
Worker::Wake()
whileWorker::ThreadProc
is doing work will result inWorker::ThreadProc
performing a single additional iteration of work.
Code:
#include <boost/thread.hpp>
class Worker
{
public:
Worker();
~Worker();
void Wake();
private:
Worker(Worker const& rhs); // prevent copying
Worker& operator=(Worker const& rhs); // prevent assignment
void ThreadProc();
enum state { HAS_WORK, NO_WORK, SHUTDOWN };
state m_State;
boost::mutex m_Mutex;
boost::condition_variable m_Condition;
boost::thread m_Thread;
};
Worker::Worker()
: m_State(NO_WORK)
, m_Mutex()
, m_Condition()
, m_Thread()
{
m_Thread = boost::thread(&Worker::ThreadProc, this);
}
Worker::~Worker()
{
// Create scope so that the mutex is only locked when changing state and
// notifying the condition. It would result in a deadlock if the lock was
// still held by this function when trying to join the thread.
{
boost::lock_guard<boost::mutex> lock(m_Mutex);
m_State = SHUTDOWN;
m_Condition.notify_one();
}
try { m_Thread.join(); }
catch ( const boost::thread_interrupted& ) { /* suppress */ };
}
void Worker::Wake()
{
boost::lock_guard<boost::mutex> lock(m_Mutex);
m_State = HAS_WORK;
m_Condition.notify_one();
}
void Worker::ThreadProc()
{
for (;;)
{
// Create scope to only lock the mutex when checking for the state. Do
// not continue to hold the mutex wile doing busy work.
{
boost::unique_lock<boost::mutex> lock(m_Mutex);
// While there is no work (implies not shutting down), then wait on
// the condition.
while (NO_WORK == m_State)
{
m_Condition.wait(lock);
// Will wake up from either Wake() or ~Worker() signaling the condition
// variable. At that point, m_State will either be HAS_WORK or
// SHUTDOWN.
}
// On shutdown, break out of the for loop.
if (SHUTDOWN == m_State) break;
// Set state to indicate no work is queued.
m_State = NO_WORK;
}
// do some work here
}
}
Note: As a personal preference, I opted to not allocated boost::thread
on the heap, and as a result, I do not need to manage it via boost::scoped_ptr
. boost::thread
has a default constructor that will refer to Not-a-Thread, and it is move-assignable.
Related Topics
Create Objects in Conditional C++ Statements
Xlib How Does This (Removing Window Decoration) Work
Opencv Cvsaveimage Jpeg Compression Factor
Rotating a 2D Pixel Array by 90 Degrees
Qt - Qpushbutton Text Formatting
Compiler Support for Upcoming C++0X
How to Set the Background Image in Qt Stylesheet
Building Qt 4.5 with Visual C++ 2010
How to Know When a New Usb Storage Device Is Connected in Qt
What Is a Possible Workaround for Object Slicing in C++
Correct Way to Check If Windows Is 64 Bit or Not, on Runtime? (C++)
Using Boost::Iostreams::Tee_Device
C++ Get String from Clipboard on Linux
Checking for Duplicates in a Vector
Getopt Fails to Detect Missing Argument for Option
C++ Back End Call the Python Level Defined Callbacks with Swig Wrapper
Gcc 7, -Wimplicit-Fallthrough Warnings, and Portable Way to Clear Them