Intel® C++ Compiler 16.0 User and Reference Guide

Holding a Lock Across a Strand Boundary

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:

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: