views:

273

answers:

3

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?

+10  A: 

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.

Greg Bacon
Even something as simple as the addresses of this vs rhs would work. always lock the one with the lower address first.
Kyle Butt
clone would only work well if it weren't copying, and I don't think implicit sharing will work, but I'll take a look.Interesting approach Kyle. I can't see any flaws.
pomeroy
A: 

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

Shing Yip
A: 

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
}
RED SOFT ADAIR
To avoid a deadlock, it's best to introduce a livelock? And spin, using 100% CPU?
bk1e