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?
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.
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.
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.
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.
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.
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...
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With