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 cha
this is a known problem already there is a std solution.
std::lock()
can be called on 2 or more mutex at the same time whilst avoiding deadlock's.
More information here
it does offer a recommendation.
std::scoped_lock offers a RAII wrapper for this function, and is generally preferred to a naked call to std::lock.
of course this doesn't really allow early releases of one lock above the other so use std::defer_lock
or std::adopt_lock
like I did in this answer to a similar question.
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...
To avoid a deadlock its probably best, to wait until both resources can be locked:
Dont know which mutex API you are using so here is some arbitrary pseudo code, assume that can_lock()
only checks if it can lock a mutex, and that try_lock()
returns true if it did lock, and false, if the mutex is already locked by somebody else.
void foo::copy(const foo & rhs)
{
for(;;)
{
if(! pMutex->cany_lock() || ! rhs.pMutex->cany_lock())
{
// Depending on your environment call or dont call sleep()
continue;
}
if(! pMutex->try_lock())
continue;
if(! rhs.pMutex->try_lock())
{
pMutex->try_lock()
continue;
}
break;
}
// do copy
}
You can try locking both the mutexes at the same time using scoped_lock or auto_lock.... like bank transfer do...
void Transfer(Receiver recv, Sender send)
{
scoped_lock rlock(recv.mutex);
scoper_lock slock(send.mutex);
//do transaction.
}
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.
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.