3

It is a common practice to use guards, RAII and try/catch blocks to ensure that a thread will be eventually joined in all cases. However, what about threads that are meant to be detached?

void aFunction() {
   std::thread t {someOtherFunction}; // someOtherFunction may throw!!!
   t.detach;
}

Am I supposed to use some exception handling mechanism to make sure that t will be properly detached even in the case of an exception or does it not really matter?

Niall
  • 30,036
  • 10
  • 99
  • 142
Marinos K
  • 1,779
  • 16
  • 39

3 Answers3

5
std::thread t {someOtherFunction}; // someOtherFunction may throw!!

If someOtherFunction can throw, then the exception needs to be handled in that function. If not, the default behaviour is to call std::terminate if the exception "escapes" the thread function.

The std::thread object construction and std::thread::detach itself can also throw, so these exceptions need to be handled by the aFunction().

The detach() method throws if thread object is not joinable. So if you wish to avoid handling this exception, you can add a test to see if the thread is joinable before attempting to detach it.

if (t.joinable())
  t.detach;

The fact that you are called detach() indicates that you do not wish to "join" (synchronise with) the thread at a later stage and it will basically take care of itself.

As with most exception handling questions, what are you going to do with the exception that is thrown? If the exception cannot be handled, then either don't catch it or catch it, log it and rethrow it (or call std::terminate).

Niall
  • 30,036
  • 10
  • 99
  • 142
  • 1
    _If the exception cannot be handled, then the best is to catch it, log it and rethrow it_ - this way you lose the original throw site in the stack trace. IMO, a better way is to catch all unhandled exceptions in `main` function try-catch block, output the message and the stack trace and terminate. In other words, never catch an exception if you do not handle it. – Maxim Egorushkin Feb 02 '16 at 10:34
  • I handle all exceptions globally in my main, so I'm not really worried about t throwing an exception, I will catch it there. From what I understand, however, is that if someOtherFunction throws an exception then std::thread will call terminate() immediately rather than allowing this exception to propagate for main to catch it, right? – Marinos K Feb 02 '16 at 10:37
  • @MarinosK. Correct, an unhanded exception in a thread function results in std::terminate being called. It doesn't propagate back to main. You could build such a mechanism, but this is actionable on the side of the client code (being you). – Niall Feb 02 '16 at 10:39
  • @MaximEgorushkin. True, all the alternatives have some pros and cons associated with them. The catch is to find and pick one that offers the best mix given the context of the application. – Niall Feb 02 '16 at 10:47
  • Is your solution of calling joinable() and then detach() safe? what if the thread exits after the joinable() call returns but before detach() is called? Won't t throw in that case since it isn't joinable? – Eric Zinda Nov 13 '18 at 17:51
  • @EricZinda, yes it is fine. The thread, once started is considered joinable until it is joined. From https://en.cppreference.com/w/cpp/thread/thread/joinable, “_a thread that has finished executing code, but has not yet been joined is still considered an active thread of execution and is therefore joinable_“. – Niall Nov 13 '18 at 20:22
1

The fact that ~thread calls std::terminate if not joined is considered a flaw in the standard.

Scott Meyers recommends using ThreadRAII wrapper that joins the thread in the destructor if it is still joinable:

class ThreadRAII {
public:
  ThreadRAII(std::thread&& thread): t(std::move(thread)) {}
  ~ThreadRAII() { if (t.joinable()) (t.join(); }

private:
  std::thread t;
};

If someOtherFunction throws an exception in another thread and no one catches that exception, std::terminate is called.

Use std::current_exception and std::rethrow_exception to catch the exception, transport it to another thread and re-throw it. E.g.:

void thread_function(std::exception_ptr* exception)
try {
    throw std::runtime_error("test exception");
}
catch(std::exception& e) {
    *exception = std::current_exception();
}

int main() {
    // Thread creation.
    std::exception_ptr other_thread_exception;
    std::thread t{thread_function, &other_thread_exception};

    // Thread termination.
    t.join();
    if(other_thread_exception)
        std::rethrow_exception(other_thread_exception);
}
Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271
1

Am I supposed to use some exception handling mechanism to make sure that t will be properly detached even in the case of an exception or does it not really matter?

What do you mean "properly detached even in the case of an exception"? What kind of "improper" detaching are you worried about?

Just detach it immediately, and there's no question of exception safety:

std::thread t{something};
t.detach();

If the constructor of t throws then there's no thing to detach. If it doesn't, you detach it as the next statement, and there's nothing that can throw before then.

Once it's detached it's not joinable, so you don't need RAII or any other mechanism to ensure it's joined or detached before you leave the scope.

Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
  • but if the thread exits before you call detach(), detach() will throw, so you'll need some more complicated locking to prevent that race condition, right? – Eric Zinda Nov 13 '18 at 17:53
  • @EricZinda _"but if the thread exits before you call detach(), detach() will throw"_ No, it won't. What makes you think that? – Jonathan Wakely Nov 13 '18 at 19:44
  • I was thinking that the thread finishing execution would make it non-joinable, but that isn't the case as per the @niall comment above. – Eric Zinda Nov 15 '18 at 00:18