views:

255

answers:

2

C++0x thread library or Boost.thread define a non-member variadic template function that locks all mutex at once that helps to avoid deadlock.

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

The same can be applied to a non-member variadic template function try_lock_until, which locks all the mutex until a given time is reached that helps to avoid deadlock like lock(...).

template <class Clock, class Duration,
          class L1, class L2, class... L3> 
void try_lock_until(
          const chrono::time_point<Clock,Duration>& abs_time, 
          L1&, L2&, L3&...);

I have an implementation that follows the same design as the Boost function boost::lock(...). But this is quite complex.

As I can be missing something evident I wanted to know if:

is there a simple timed lock algorithm avoiding deadlock on multiple mutexes?

If no simple implementation exists, can this justify a proposal to Boost?

P.S. Please avoid posting complex solutions.


Notes:

  1. I don't want to add more constraints than the ones std::lock(...) imposes.
  2. std::lock(...) doesn't avoid deadlock completely. It just avoid to have dead lock if two threads do std::lock(l1,l2) and the other std::lock(l2,l1). This is enough to avoid a lot of deadlock cases.
+4  A: 

The classic (and best) approach is to define an order in which mutexes are to be locked, and make sure that any code that holds more than one locked mutex at a time always locks its mutexes in that order. Is that approach insufficient here?

Jeremy Friesner
std::lock(...) doesn't force to give any order to the mutex. So I don't want to add this constraint for try_lock_until(...).std::lock(...) doesn't avoid deadlock. It just avoid to have dead lock if two threads do std::lock(l1,l2) and the other std::lock(l2,l1). This enough for a lot of deadlock cases.
Vicente Botet Escriba
My feeling is that if a program has thread A doing std::lock(l1, l2) and thread B doing std::lock(l2, l1), then the program is buggy. Detecting and reporting the lock-ordering problem might be more appropriate than trying to handle it.
Jeremy Friesner
Let give you an example. The transfer function between two accounts locks both account. So if thread A do transfer(ac1, ac2) and thread do transfer(ac2,ac1), the use of lock(...) in transfer avoid the deadlock.
Vicente Botet Escriba
A: 

The easiest I can think of, although it would require some serious thought as to correctness, would be to do all mutex locking is a global ordering. The implementation would be to give mutexes a serial number on creation. Then when one locks them as a set you sort them by that serial number and proceed to lock in that order. There's no need for special kinds of locks, as this would guarantee a globally consistent lock order.

GrafikRobot
std::lock(...) doenn't force to give any order to the mutex. So I don't want to add this constraint for try_lock_until(...)
Vicente Botet Escriba
I guess "easiest" is a misnomer ;-) The ordering I'm suggesting would not be part of `try_lock_until`, it would be part of the mutex (i.e. the `Lockable`) since a regular mutex identifier is not sufficient for ordering as they are reused. So what I suggest is not easy in that it would require changing the mutex implementation. And hence in that sense it could be considered complex enough, because of the architectural change, to need implementing on the Boost or STD side.
GrafikRobot
Global ordering doesn't scales. Do you use already this algorithm in your applications?
Vicente Botet Escriba
I don't see how global ordering doesn't work.. Especially since this is exactly what Jeremy F. posted as the, now accepted, answer. I'm just providing you with the specific, efficient, modular, and not error-prone-human-labor way to implement exactly the same.And no, I haven't used this. Which I kinda mentioned ;-) I don't use things like this because I just don't end up writing multi-thread programs that end up needing to lock many resources as it's just plain bad multi-thread design ;-)
GrafikRobot