What happens when an exception goes unhandled in a multithreaded C++11 program?
If I have a C++11 program running two threads, and one of them throws an unhandled exception, what happens? Will the entire program die a fiery death? Will the thread where the exception is thrown die alone (and if so, can I obtain the exception in this case)? Something else entirely?
Nothing has really changed. The wording in n3290 is:
If no matching handler is found, the function
std::terminate()
is called
The behavior of terminate
can be customized with set_terminate
, but:
Required behavior: A
terminate_handler
shall terminate execution of the program without returning to the caller.
So the program exits in such a case, other threads cannot continue running.
Since there seems to be legitimate interest in exception propagation and this is slightly at least somewhat relevant to the question, here's my suggestion: std::thread
is to be considered an unsafe primitive to build e.g. higher level abstractions. They're doubly risky exception-wise: if an exception goes off inside the thread we just launched, everything blows up, as we've shown. But if an exception goes off in the thread that launched the std::thread
we may potentially be in trouble because std::thread
's destructor requires that *this
be either joined or detached (or equivalently, be not-a-thread). A violation of those requirements results in... a call to std::terminate
!
A code map of the dangers of std::thread
:
auto run = []
{
// if an exception escapes here std::terminate is called
};
std::thread thread(run);
// notice that we do not detach the thread
// if an exception escapes here std::terminate is called
thread.join();
// end of scope
Of course, some may argue that if we simply detach
ed every thread that we launch we're safe on that second point. The problem with that is that in some situations join
is the most sensible thing to do. The 'naive' parallelization of quicksort for instance requires to wait until the subtasks have ended. In those situations join
serves as a synchronization primitive (a rendez-vous).
Lucky for us, those higher level abstractions I mentioned do exist and come with the Standard Library. They are std::async
, std::future
as well as std::packaged_task
, std::promise
and std::exception_ptr
. The equivalent, exception-safe version of the above:
auto run = []() -> T // T may be void as above
{
// may throw
return /* some T */;
};
auto launched = std::async(run);
// launched has type std::future<T>
// may throw here; nothing bad happens
// expression has type T and may throw
// will throw whatever was originally thrown in run
launched.get();
And in fact instead of calling get
in the thread that called async
you can instead pass the buck to another thread:
// only one call to get allowed per std::future<T> so
// this replaces the previous call to get
auto handle = [](std::future<T> future)
{
// get either the value returned by run
// or the exception it threw
future.get();
};
// std::future is move-only
std::async(handle, std::move(launched));
// we didn't name and use the return of std::async
// because we don't have to