Intel® C++ Compiler 16.0 User and Reference Guide
Intel® Cilk™ Plus attempts to reproduce, as closely as possible, the semantics of C++ exception handling. This generally requires limiting parallelism while exceptions are pending, and programs should not depend on parallelism during exception handling. The exception handling logic is as follows:
If an exception is thrown and not caught in a spawned child, then that exception is rethrown in the parent at the next sync point.
If the parent or another child also throws an exception, then the first exception that would have been thrown in the serial execution order takes precedence. The logically-later exceptions are discarded. There is currently no mechanism for collecting multiple exceptions thrown in parallel.
Throwing an exception does not abort existing children or siblings of the strand in which the exception is thrown; these strands will run normally to completion.
If a try block contains a cilk_spawn and/or a cilk_sync, then there is an implicit cilk_sync just before entering the try block and an implicit cilk_sync at the end of the try block (after destructors are invoked). A sync is executed automatically before exiting a try block, function block, or cilk_for body via an exception. (Note that the scope of a sync within a cilk_for is limited to spawns within the same loop.) A function has no active children when it begins execution of a catch block. These implicit syncs exist to ensure that the same catch clauses run as would have run in a serial execution of the program.
Implicit syncs may limit the parallelism of your program. The sync before a try block, in particular, may prematurely sync a spawn that occurs before the try block as in the following example:
void func() { cilk_spawn f(); try { // oops! implicit sync prevents parallel execution of f() cilk_spawn g(); h(); } catch (...) { // Handle exceptions from g() or h(), but not f() } }
If this is a problem for your program, there are two ways to prevent the implicit sync. One way is to move the body of your try block into a separate function. By moving the cilk_spawn lexically out of the try block, you eliminate the automatic generation of cilk_sync at both the start and end of the try block. The previous example could be rewritten as follows:
void gh() { cilk_spawn g(); h(); } void func() { cilk_spawn f(); try { // no cilk_spawn in body, so no implicit sync gh(); } catch (...) { // Handle exceptions from gh(), but not f() } }
Another way to prevent an implicit sync is to enclose the body of the try block or the entire try/catch statement in a cilk_for loop that executes only once. The body of a cilk_for loop is an isolated context such that spawns and syncs do not interact with the surrounding function. If you do this a lot, a macro can come in handy. The above example could be rewritten as follows:
#define CILK_TRY cilk_for (int _temp = 0; _temp < 1; ++_temp) try void func() { cilk_spawn f(); CILK_TRY { // try is within cilk_for, so does not sync f() cilk_spawn g(); h(); } catch (...) { // Handle exceptions from g() or h(), but not f() } }
Windows* Structured Exception Handling
There are restrictions when using Microsoft Windows* structured exception handling (specifically, the /EHa compiler option and the __try, __except, __finally and __leave extensions to C/C++). Structured exception handling (SEH) will fail if an SEH exception is thrown after a steal occurs and before the corresponding cilk_sync. The runtime recognizes this condition and terminates the program with an appropriate error code.