Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Acquire a lock on two mutexes and avoid deadlock

The following code contains a potential deadlock, but seems to be necessary: to safely copy data to one container from another, both containers must be locked to prevent changes from occurring in another thread.

void foo::copy(const foo & rhs)
{
    pMutex->lock();
    rhs.pMutex->lock();
    // do copy
}

Foo has an STL container and "do copy" essentially consists of using std::copy. How do I lock both mutexes without introducing deadlock?

like image 740
pomeroy Avatar asked Feb 12 '10 03:02

pomeroy


People also ask

How can we avoid deadlock mutex?

One of the most common ways of avoiding a deadlock is to always lock the two mutexes in the same order. If we always lock mutex A before mutex B, then we'll never have a deadlock.

Can mutexes be used to handle deadlocks?

Mutexes are namable and hence you can signal whether the Mutexis acquired or not by some other thread within a process or across processes. You should note a mutex is created with the call to Win32 CreateMutex. You can check the ownership of the Mutex using WaitAll, WaitAny and so on calls and that avoids deadlocks.

Can a thread lock multiple mutexes?

Mutexes are used to prevent multiple threads from causing a data race by accessing the same shared resource at the same time. Sometimes, when locking mutexes, multiple threads hold each other's lock, and the program consequently deadlocks.

How can a deadlock be avoided?

Deadlock can be prevented by eliminating any of the four necessary conditions, which are mutual exclusion, hold and wait, no preemption, and circular wait. Mutual exclusion, hold and wait and no preemption cannot be violated practically. Circular wait can be feasibly eliminated by assigning a priority to each resource.


3 Answers

Impose some kind of total order on instances of foo and always acquire their locks in either increasing or decreasing order, e.g., foo1->lock() and then foo2->lock().

Another approach is to use functional semantics and instead write a foo::clone method that creates a new instance rather than clobbering an existing one.

If your code is doing lots of locking, you may need a complex deadlock-avoidance algorithm such as the banker's algorithm.

like image 72
Greg Bacon Avatar answered Oct 14 '22 05:10

Greg Bacon


How about this?

void foo::copy(const foo & rhs)
{
    scopedLock lock(rhs.pMutex); // release mutex in destructor
    foo tmp(rhs);
    swap(tmp); // no throw swap locked internally
}

This is exception safe, and pretty thread safe as well. To be 100% thread save you'll need to review all code path and than re-review again with another set of eyes, after that review it again...

like image 40
Shing Yip Avatar answered Oct 14 '22 04:10

Shing Yip


As @Mellester mentioned you can use std::lock for locking multiple mutexes avoiding deadlock.

#include <mutex>

void foo::copy(const foo& rhs)
{
    std::lock(pMutex, rhs.pMutex);

    std::lock_guard<std::mutex> l1(pMutex, std::adopt_lock);
    std::lock_guard<std::mutex> l2(rhs.pMutex, std::adopt_lock);

    // do copy
}

But note to check that rhs is not a *this since in this case std::lock will lead to UB due to locking same mutex.

like image 29
4xy Avatar answered Oct 14 '22 05:10

4xy