Boost logo

Boost :

From: Phil Endecott (spam_from_boost_dev_at_[hidden])
Date: 2008-02-01 11:31:35


>Kowalke Oliver wrote:
> what's the best practice in following scenario:
> m threads and n resources (each resource is protected by a mutex)
> thread x needs read/write access to resources (...i,j,k,..)
> threads y,z,... do access another subset of resources at the same time
>
> inorder to protected deadlocks I would do following in the code of the
> different threads (code executed by the threads are different):
>
> ...
> try_lock: // label
> unique_lock< mutex > lk1( mtx1, try_to_lock_t);
> unique_lock< mutex > lk4( mtx2, try_to_lock_t);
> unique_lock< mutex > lk7( mtx3, try_to_lock_t);
> if ( ! ( lk1 && lk2 && lk3) ) goto try_lock;
> // all mutexes are locked
> // now modify safely resource 1,2,3
> ...
>
> Maybe you have a better pattern do solve this problem?

N2447 proposes a generic locking algorithm. Quote:

     template <class L1, class L2, class ...L3> void lock(L1&, L2&, L3&...);

         Requires: Each template parameter type must supply the
following member functions with semantics corresponding to the Mutex
concept, except that try_lock is allowed to throw an exception [Note:
The unique_lock class template meets these requirements when suitable
instantiated. --end note]

             void lock();
             bool try_lock();
             void unlock();

         Effects: All arguments are locked with an algorithm that avoids
deadlock. If an exception is thrown by a call to lock() or try_lock(),
there are no effects.

End Quote.

Presumably the intention is that you use it like this:

{
   unique_lock< mutex > lk1( mtx1, defer_lock);
   unique_lock< mutex > lk4( mtx2, defer_lock);
   unique_lock< mutex > lk7( mtx3, defer_lock);
   lock(lk1,lk2,lk3);
   ....critical section....
} // locks are released when they go out of scope

I guess that a multi-lock could be built on top of this, for more
concise code:

struct multilock {
   unique_lock lk1; unique_lock lk2; unique_lock lk3;
   multilock(mutex& mtx1, mutex& mtx2, mutex& mtx3):
     lk1(mtx1, defer_lock), lk2(mtx2, defer_lock), lk3(mtx3, defer_lock)
     { lock(lk1,lk2,lk3); }
};

{
   multilock mlk(mtx1,mtx2,mtx3);
   ...critical section....
}

This is not included in Boost.Threads (AFAIK), and doing so without
C++0x varargs templates is a bit messy.

The obvious (to me!) implementation of the lock algorithm is to order
the mutexes. This can be done based on their addresses, if every
instance of the same underlying mutex has the same address. This
doesn't work for shared-memory mutexes; maybe the mutex itself should
be required to be less-than-comparable?

Anyway, you can easily write a version for a fixed number of locks:

void lock(lock& lk1, lock& lk2) {
   if (lk1.mutex() < lk2.mutex()) {
     lk1.lock();
     lk2.lock();
   } else {
     lk2.lock();
     lk1.lock();
   }
}

Phil.


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk