Question

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?

Was it helpful?

Solution

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'

OTHER TIPS

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 );
}

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.

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.

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.

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.

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.

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.

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top