简体   繁体   中英

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?

Impose some kind of total order on instances of foo and always acquire their locks in either increasing or decreasing order, eg , 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.

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.

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.
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM