3

I understand I can use a mutex member in a class and lock it inside each method to prevent data race in multithreading environment. However, such method might incur deadlock if there are nested calls in the class's methods, like the add_one() and add_two() in below class. Using different mutex for each method is a workaround. But are there more principled and elegant way to prevent deadlock in the case of nested calls?

class AddClass {
public:
  AddClass& operator=(AddClass const&) = delete; // disable copy-assignment constructor
  AddClass(int val) : base(val) {}
  int add_one() { return ++base; }
  int add_two() { 
    add_one; 
    add_one;
    return base;
  }
private:
  int base;
};
ROBOT AI
  • 1,217
  • 3
  • 16
  • 27

4 Answers4

9

There is std::recursive_mutex exactly for this purpose.

Another approach that avoids overhead incurred by recursive mutex is to separate public synchronized interface from private non-synchronized implementation:

class AddClass {
public:
  AddClass& operator=(AddClass const&) = delete; // disable copy-assignment constructor
  AddClass(int val) : base(val) {}
  int add_one() {
     std::lock_guard<std::mutex> guard{mutex};
     return add_one_impl();
  }
  int add_two() {
     std::lock_guard<std::mutex> guard{mutex};
     return add_two_impl();
  }
private:
  int base;
  std::mutex mutex;
  int add_one_impl() { 
    return ++base;
  }
  int add_two_impl() { 
    add_one_impl(); 
    add_one_impl();
    return base;
  }
};

Note, however, that this is not always possible. For example if you have a method that accepts a callback and calls it while holding the lock, the callback might try to call some other public method of your class, and you are again faced with the double locking attempt.

yuri kilochek
  • 12,709
  • 2
  • 32
  • 59
  • 2
    the separation of concerns of locking and implementation is the correct approach. recursive mutex should be a last resort. +1 – Richard Hodges Jan 01 '17 at 16:37
1

A recursive mutex is a lockable object, just like mutex, but allows the same thread to acquire multiple levels of ownership over the mutex object.

When and how to Use Recursive Mutex-- Link Below

Recursive Mutex

Note: Recursive and non-recursive mutexes have different use cases.

Hope it helps.

Community
  • 1
  • 1
Ranadip Dutta
  • 8,857
  • 3
  • 29
  • 45
0

The general solution for this is called a reentrant mutex

While any attempt to perform the "lock" operation on an ordinary mutex (lock) would either fail or block when the mutex is already locked, on a recursive mutex this operation will succeed if and only if the locking thread is the one that already holds the lock. Typically, a recursive mutex tracks the number of times it has been locked, and requires equally many unlock operations to be performed before other threads may lock it.

There's one in the C++11 standard library: http://en.cppreference.com/w/cpp/thread/recursive_mutex

Shea Levy
  • 5,237
  • 3
  • 31
  • 42
0

Implement the public interface with functions that lock your locks and call private member functions that do the work. The private functions can call each other without the overhead of re-locking mutexes and without resorting to recursive mutexes, which are regarded by many as a sign of design failure.

Pete Becker
  • 74,985
  • 8
  • 76
  • 165