Exact exception propagation(exception_ptr) in the Intel® Threading Building Blocks (Intel® TBB) library and C++11

Exceptions and multithreading in C++03 and C++11

Multithreading is not uncommon these days, nor is using exceptions for error handling. So a user of the Intel® Threading Building Blocks (Intel® TBB) library may reasonably expect that exceptions raised during execution of a parallel algorithm are transferred to the master thread and thrown from a call to parallel algorithm function.

For example, given a code throwing the my_error exception:

struct my_error;
struct parallel_for_body {
    .....
    void operator()( const tbb::blocked_range<int>& range ) const {
        ... 
        if (error){
            throw my_error();
        }
    }
};

It is reasonable to expect handling it in the thread that calls parallel algorithm:

try {
    tbb::parallel_for( tbb::blocked_range<int>( 0, SomeNumber ),  parallel_for_body() );
} catch(my_error& ve){
    //some handler code   
}

During execution of a parallel algorithm, user-written code is executed by several worker threads wrapped by internal library code. In order to achieve the exception handling model described above, the library must:

  1. Catch the exception in the worker thread
  2. Pass the caught exception to the master thread and re-throw it there

In C++03 it was impossible to transfer every caught exception between threads without either losing some information (slicing) or requiring that all possible exceptions were specified in advance. The reason is that the standard had no concept of threads. Thus, in C++03, in order to transfer an exception between two parts of user-written code which may be running in different threads, the Intel TBB library has to transform all user exceptions into exceptions of type tbb::captured_exception . So you need to handle a tbb::captured_exception coming out of Intel TBB calls, instead of the exception that was really thrown.

C++11 introduced the ability to propagate exception between threads [1] , which is very handy for threading library such as Intel TBB.

We can describe this "exact" exception propagation with a short example from one of our previous blogs [2]:

"In fact it can be described in five words - it propagates any exception unchanged.

Thus, if your parallel code can throw, say, std::out_of_range and CMyDataCorruption, then you can write

try {
	parallel_algorithm (/* … */);
} catch ( std::out_of_range& e ) {
	// handler code
} catch ( CMyDataCorruption& e ) {
	// handler code
}

"

With the help of this C++11 feature exception handling becomes “natural” for Intel TBB users so they are able to catch the same exception type as that which was thrown in their code, no matter which thread throws it.

The enhancement

Starting in version 4.2 Intel TBB supports “exact” exception propagation out of the box for OS X*, Windows* and Linux*operating systems.

How to enable exact exception handling

Some requirements have to be satisfied before you can use exact exception propagation:

  1. A compiler that supports C++11 std::exception_ptr. Minimally required compiler versions: GCC* 4.4, Microsoft* Visual C++ 2010, Clang* 2.9, Intel C++ Compiler 12 (On OS X : Intel C++ Compiler 14).
  2. Code that uses Intel TBB must be compiled with C++11 mode turned on:
    • GCC/Clang/Intel C++ Compiler for Linux /OS X:  -std=c++11 switch has to be used
    • Visual C++/Intel C++ Compiler for Windows: it is turned on automatically when available
  3. For OS X: libc++ (Clang C++ standard library) has to be used [3]; it is the only way to get exact exception propagation, as well as any other library dependent C++11 features [4]. To enable it:
    • for both Clang and Intel C++ Compiler, -stdlib=libc++ has to be used, i.e.:
      clang++ -stdlib=libc++ -std=c++11 concurrent_code.cpp -ltbb 
  4. An appropriate Intel TBB binary should be linked against. 

Choosing an appropriate Intel TBB binary

Since Intel TBB is a portable cross platform library it has to support both modes of user exception propagation: transforming them into tbb::captured_exception (C++03 mode) and using exact exception propagation (C++11 mode).

In order to support exact exception propagation the Intel TBB library has to be built with C++11 support enabled, by using an appropriate compiler and standard library. That is why there is more than one set of binaries shipped on each platform.

Windows*

On Windows, we support exact exception propagation for Microsoft Visual Studio 2010 and above since Intel TBB version 2.2.

Linux*

In order to keep backward compatibility the Intel TBB 4.2 package ships two sets of binaries:

  • Binaries linked with GCC 4.1.2 runtime, located in lib\{ia32,intel64}\gcc4.1
  • Binaries linked with GCC 4.4.4 runtime and C++11 support turned on, located in lib\{ia32,intel64}\gcc4.4.

The first is intended to be used with GCC runtimes starting from 4.1.2 and higher in C++03 mode (transforming all exceptions into tbb::captured_exception). The second one is intended to be used with runtime of GCC 4.4.4 and higher in both C++03 and C++11 (exact exception propagation) mode.

NOTE: These runtimes are not fully interchangeable. In particular an application linked with the GCC 4.4.4 runtime cannot be run with the GCC 4.1.2 runtime.

OS X*

In order to maintain backward compatibility the Intel TBB 4.2 package ships two sets of binaries:

  • One that is linked with GCC 4.1.2 runtime
  • One that is linked with libc++ , with C++11 support turned on (located in the libc++ sub folder).

The first is intended for use with GCC runtimes and C++03. The second is intended to use with Clang runtime in both C++11 and C++03 mode.

NOTE: these runtimes are not fully interchangeable. You cannot link against one library and then run using the other.

Setting up environment

The easiest way to make appropriate libraries visible to a linker is to use the tbbvars environment setup script from the TBB package.

  • on OS X the libc++ argument should be specified to link with clang runtime enabled TBB :
    tbbvars.sh libc++ 
  • on Linux the library is chosen automatically based on the version of gcc which is detected:
     tbbvars.sh intel64 
  • on Windows the version of Microsoft Visual Studio used has to be specified:
     tbbvars.bat intel64 vs2010 

Building the library from sources

If you use the open source distribution of Intel TBB, you should use two makefile variables that affect building process:

  • cpp0x – enables/disables c++11 support in Intel TBB
  • stdlib – enables the use of libc++ as a C++ standard library for Clang and Intel C++ Compiler on OS X.

So, to build the exact exception propagation enabled library with the Intel C++ Compiler, use the following commands:

  • On Windows:
    make tbb compiler=icl
  • On Linux:
    make tbb compiler=icc cpp0x=1
  • On OS X:
    make tbb compiler=icc cpp0x=1 stdlib=libc++

Final words

Exact exception propagation is one of benefits brought by C++11 to Intel TBB users: it makes use of exceptions with parallel algorithms no different from that with serial functions. Available out-of-the box for Windows, Linux and OS X operating systems in the recent versions of Intel TBB, it is now the recommended way for exception handling. The old approach based on class captured_exception is obsolete for C++11, though still supported for backward compatibility

REFERENCES :

  1. "Copying and rethrowing exceptions"
  2. "Exception handling in TBB 2.2 - Getting ready for C++0x"
  3. libc++
  4. "Clang and standard libraries on Mac OS X"
For more complete information about compiler optimizations, see our Optimization Notice.