In the first thread so:

mutex1.lock(); mutex2.lock(); 

In the second so:

 mutex2.lock(); mutex1.lock(); 

The problem is that these mutexes are in different classes, say Parent and Child , they call each other's methods, but they know nothing about each other's internal structure (but if necessary, they can be taught, but just how?). Mutexes protect some internal data of the corresponding classes.

This happens with nested calls:

Callstack - thread 1

 10. mutex2.lock(); 9. child.method2(); 8. mutex1.lock(); 7. parent.method1(); .... 

Callstack - thread 2

 10. mutex1.lock(); 9. parent.anotherMethod(); 8. mutex2.lock(); 7. child.someMethod(); .... 

Maybe there is some kind of pattern for such a problem.

  • one
    No matter how it sounds, the only way to avoid deadlocks is not to use mutexes. And, as practice shows, in a very large number of cases, they can be avoided. - ixSci

3 answers 3

To get rid of deadlocks, you can use the std::lock function. It accepts several mutexes, and locks them using a special algorithm that allows you to avoid deadlocks.

It should be applied in this way:

 std::lock(mutex1, mutex2); std::lock_guard<std::mutex> guard1(mutex1, std::adopt_lock); std::lock_guard<std::mutex> guard2(mutex2, std::adopt_lock); 

Or vice versa, first create lock_guard and then lock:

 std::lock_guard<std::mutex> guard1(mutex1, std::defer_lock); std::lock_guard<std::mutex> guard2(mutex2, std::defer_lock); std::lock(mutex1, mutex2); 
  • As I understand it, the problem with the TC is that the classes have one mutex each and do not know about each other. So in this case, the idea does not help. - VladD
  • one
    But the technique is good, did not know about it. +1 - VladD
  • one
    I did not know either. But the description / I wonder what the additional delay and in what situations? - avp

Update: In your case, when each class has its own mutex, and the problem is only in the order of calls, I would not recommend to call someone else's code under blocking. Just because this code has the full right to lock some other mutex. Calling someone else's code under a locked mutex is almost always a deadlock danger.

Try reorganizing the code to look like this:

 void obj1.method1() { { std::lock_guard g1(mutex1); // Ρ€Π°Π±ΠΎΡ‚Π°Π΅ΠΌ с Π΄Π°Π½Π½Ρ‹ΠΌΠΈ/Π·Π°ΠΏΠΎΠΌΠΈΠ½Π°Π΅ΠΌ ΠΈΡ… } // Ρ‚ΠΎΠ»ΡŒΠΊΠΎ здСсь ΠΌΡ‹ ΠΈΠΌΠ΅Π΅ΠΌ ΠΏΡ€Π°Π²ΠΎ Π²Ρ‹Π·Π²Π°Ρ‚ΡŒ ΠΎΠ±ΡŠΠ΅ΠΊΡ‚, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ ΠΌΡ‹ Π½Π΅ ΠΊΠΎΠ½Ρ‚Ρ€ΠΎΠ»ΠΈΡ€ΡƒΠ΅ΠΌ obj2.method2(); } 

You cannot get out of this situation. Classes using shared mutexes must cooperate.

Often the problem is solved in this way: the mutexes are renumbered, and if the code wants to take several mutexes, it must take them in ascending order of numbers.

On the other hand, you can reduce the blocking granularity, and use one mutex instead of both (if it is permissible within your task).

Also, you can try to avoid the double mutex problem by dividing the work with blocked objects into parts / entities: first lock one mutex and retrieve the necessary data, then release this mutex and lock the other using local data when working with the second object.

Another popular approach is to get rid of shared data, if possible, and work as much as possible with local data.


Summary: there is no common solution. Get out.

  • I also forgot to say that my mutexes are recursive. And almost everywhere std::lock_guard . - zenden2k
  • @ zenden2k: corrected the code in the example - VladD
  • I have 2 classes Parent and Child . Maybe I should inside the Child first lock the Parent-a mutex? Tipo parent.mutex.lock();this->mutex.lock(); True, this will introduce additional delays. - zenden2k
  • @ zenden2k: Not worth it, this will break the encapsulation. And letting go of a mutex during a call to another class is not exactly rolling? This is usually the right design. - VladD

You have already answered a lot of useful things, I would like to add a couple more points:

  • in case work with data is built in such a way that it does not avoid capturing two mutexes, it is advised to predetermine the order in which these mutexes are captured , and to follow this order in all streams;

  • In cases where the architecture does not allow predetermining the order of capture of mutexes, you can use std :: try_lock (), which, if it is impossible to capture a mutex, returns control. Thus, if a thread has captured the first mutex, but cannot capture the second, it can release the first one, perform some other routine work (instead of hanging on one of the mutexes), and return to trying to capture these mutexes again.