Intel® C++ Compiler 16.0 User and Reference Guide
The best and easiest practice is to avoid holding a lock across strand boundaries. Sibling strands can use the same lock, but there are potential problems if a parent shares a lock with a child strand. The issues are as follows:
There is no guarantee that a strand created after a cilk_spawn or cilk_sync boundary will continue to execute on the same OS thread as the parent strand. Most locking synchronization objects, such as a Windows* CRITICAL_SECTION, must be released on the same thread that allocated them.
cilk_sync exposes the application to Intel® Cilk™ Plus runtime synchronization objects. These can interact with the application in unexpected ways. Consider the following code:
Consider the following code:
#include <cilk/cilk.h> #include <tbb/mutex.h> #include <iostream> void child (tbb::mutex &m, int &a) { m.lock(); a++; m.unlock(); } void parent(int a, int b, int c) { tbb::mutex m; try { cilk_spawn child (m, a); m.lock(); throw a; } catch (...) { m.unlock(); } }
There is an implied cilk_sync at the end of a try block that contains a cilk_spawn. In the event of an exception, execution cannot continue until all children have completed. If the parent acquires the lock before a child, the application is deadlocked since the catch block cannot be executed until all children have completed, and the child cannot complete until it acquires the lock. Using a "guard" or "scoped lock" object won't help, because the guard object's destructor will not run until the catch block is exited.
To make the situation worse, invisible try blocks are present. Any compound statement that declares local variables with non-trivial destructors has an implicit try block around it. Therefore, by the time the program spawns or acquires a lock, it is probably already in a try block.
If a function holds a lock that could be acquired by a child, the function should not do anything that might throw an exception before it releases the lock. However, since most functions cannot guarantee that they won't throw an exception, follow these rules:
Do not acquire a lock that might be acquired by a child strand. In other words, lock against your siblings, but not against your children.
If you need to lock against a child, put the code that acquires the lock, performs the work, and releases the lock into a separate function and call it rather than putting the code in the same function as the spawn.
If a parent strand needs to acquire a lock, set the values of one or more primitive types, perhaps within a data structure, then release the lock. This is safe, provided there are no try blocks, function calls that may throw (including overloaded operators), spawns or syncs involved while holding the lock. Be sure to pre-compute the primitive values before acquiring the lock.