views:

1217

answers:

9

What are the tensions between multithreading and exception-safety in C++? Are there good guidelines to follow? Does a thread terminate because of an uncaught exception?

+3  A: 

I believe the C++ standard does not make any mention of multithreading - multithreading is a platform-specific feature.

I'm not exactly sure what the C++ standard says about uncaught exceptions in general, but according to this page, what happens is platform-defined, and you should find out in your compiler's documentation.

In a quick-and-dirty test I did with g++ 4.0.1 (i686-apple-darwin8-g++-4.0.1 to be specific), the result is that terminate() is called, which kills the entire program. The code I used follows:

#include <stdio.h>
#include <pthread.h>

void *threadproc(void *x)
{
  throw 0;

  return NULL;
}

int main(int argc, char **argv)
{
  pthread_t t;
  pthread_create(&t, NULL, threadproc, NULL);

  void *ret;
  pthread_join(t, &ret);

  printf("ret = 0x%08x\n", ret);

  return 0;
}

Compiled with g++ threadtest.cc -lpthread -o threadtest. Output was:

terminate called after throwing an instance of 'int'
Adam Rosenfield
I was just going to write the same thing. Just make it more clear that the application will terminates (somebody may interpret that just the thread is killed with terminate). terminate() will cause all threads to be killed without further processing happening.
Martin York
PS. This is the behavior of all pthread libraries I have used.
Martin York
A: 

I don't recommend letting any exception remain uncaught. Wrap your top-level thread functions in catch-all handlers that can more gracefully (or at least verbosely) shut down the program.

HUAGHAGUAH
Although this *can* make sense for some applications I disagree with it as general advice. Usually I recommend people handle the exceptions when it makes sense to, otherwise let them propagate. If you've used RAII correctly shutting down ought to not be a problem.
MattyT
Except that your RAII destructors might not get called if the exception is not caught. It's up to the implementation whether an uncaught exception unwinds the stack or not. It can just abort. If you catch everything at the top, you guarantee stack unwinding.
Steve Jessop
@onebyone: According to what paragraph in the spec? Just like to know as all C++ gurus (Herb Sutter etc) recommend writing code that doesn't use try/catch but instead is "exception-agnostic".
Andreas Magnusson
I was not referring to destructor problems. If you don't handle the exception, the entire program gets killed (right?).Depending on the task being done, you could spawn a new thread for the threadpool, finish some I/O before exiting, print extra debugging info, etc.
HUAGHAGUAH
Usually this is what you want in C++. C++ exceptions != Java exceptions. If you look at the kind of exceptions defined in C++, they are of the kind bad_alloc, logic_error, runtime_error and out_of_bounds. Not EndOfFileException and the like. In C++ exceptions are for errors and not flow control.
Andreas Magnusson
A: 

I think the most important thing is to remember that uncaught exceptions from other threads do not show to the user or thrown at the main thread. So you have to warp all of the code that should run on threads different than the main thread with try/catch blocks.

Dror Helper
+2  A: 

An uncaught exception will call terminate() which in turn calls the terminate_handler (which can be set by the program). By default the terminate_handler calls abort().

Even if you override the default terminate_handler, the standard says that the routine you provide "shall terminate execution of the program without returning to the caller" (ISO 14882-2003 18.6.1.3).

So, in summary, an uncaught exception will terminate the program not just the thread.

As far as thread safety goes, as Adam Rosenfield says, that's a platform specific thing that's not addressed by the standard.

Michael Burr
+2  A: 

This is the single biggest reason that Erlang exists.

I don't know what the convention is, but imho, be as Erlang-like as possible. Make heap objects immutable and set up some kind of message passing protocol to communicate between threads. Avoid locks. Make sure the message passing is exception-safe. Keep as much stateful stuff on the stack.

jganetsk
+2  A: 

As others have discussed, concurrency (and thread-safety in particular,) is an architectural issue, that affects how you design your system and your application.

But I would like to take your question about tension between exception-safety and thread-safety.

At the class level thread-safety requires changes to the interface. Just like exception-safety does. For example, it is customary for classes to return references to internal variables, say:

class Foo {
public:
  void set_value(std::string const & s);

  std::string const & value() const;
};

If Foo is shared by multiple threads, trouble awaits you. Naturally, you could put a mutex or other lock to access Foo. But soon enough, all C++ programmers would want to wrap Foo into a "ThreadSafeFoo". My contention, is that the interface for Foo should be changed to:

class Foo {
public:
  void set_value(std::string const & s);

  std::string value() const;
};

Yes, it is more expensive, but it can be made thread-safe with locks inside Foo. IMnsHO this creates a certain amount of tension between thread-safety and exception-safety. Or at least, you need to perform more analysis as each class used as a shared resource needs to be examined under both lights.

coryan
+1  A: 

There are two issues I noticed:

  • in g++ on Linux, the killing of a thread (pthread_cancel) is accomplished by throwing an "unknown" exception. On one hand, that lets you clean up nicely when the thread is being killed. On the other hand, if you catch that exception and do not rethrow it, your code ends with abort(). Therefore, if you or any of the libraries you use kill threads, you can't have

    catch(...)

without

throw;

in your threaded code. Here is a reference to this behavior on the web:

  • Sometimes you need to transfer exception between threads. This is not an easy thing to do - we ended up doing somehack, when the proper solution is the kind of marshalling /demarshalling you'd use between processes.
Arkadiy
+2  A: 

C++0x will have Language Support for Transporting Exceptions between Threads so that when a worker thread throws an exception the spawning thread can catch or rethrow it.

From the proposal:

namespace std {

    typedef unspecified exception_ptr;

    exception_ptr current_exception();
    void rethrow_exception( exception_ptr p );

    template< class E > exception_ptr copy_exception( E e );
}
Motti
Note that this functionality already exists in boost.
Greg Rogers
A: 

One classic example (can't remember where I saw it first) is in the std library.

Here's how you pop something from a queue:

T t;
t = q.front(); // may throw
q.pop();

This interface is somewhat obtuse compared to:

T t = q.pop();

But is done because the T copy assignment can throw. If the copy throws after the pop happens, that element is lost from the queue, and can never be recovered. But since the copy happens before the element is popped, you can put arbitrary handling around the copy from front() in try/catch blocks.

The disadvantage is that you can't implement a queue that is thread safe with std::queue's interface because of the two steps involved. What is good for exception safety (separating out steps that can throw), is now bad for multithreading.

Your main savior in exception safety is that pointer operations are no-throw. Similarly, pointer operations can be made atomic on most platforms, so they can often be your savior in multithreaded code. You can have your cake and eat it too, but it is really hard.

Greg Rogers
I didn't understand how you concluded "you can't implement a queue that is thread safe with std::queue's interface because of the two steps involved".
Amit Kumar
Think about it - even if you lock access to the internal data structure, because that lock isn't held between the calls to front and pop, after you get the front element, some other thread could also get that same front element before you pop it off, then you lose an one element and one element is duplicated.
Greg Rogers