Boost logo

Boost :

From: Howard Hinnant (hinnant_at_[hidden])
Date: 2005-09-09 08:44:57


On Sep 9, 2005, at 7:34 AM, John Maddock wrote:

>> template <class TryLock1, class TryLock2>
>> void
>> lock(TryLock1& l1, TryLock2& l2);
>>
>> and maybe also:
>>
>> template <class TryLock1, class TryLock2, class TryLock3>
>> void
>> lock(TryLock1& l1, TryLock2& l2, TryLock3& l3);
>
> Yep, those would be useful additions to Boost.Thread IMO.
>
>> Problem: With the current interface, my "generic" lock algorithm is
>> no
>> longer generic. It has to know about read_lock() and write_lock(),
>> even though these concepts have absolutely nothing to do with how you
>> lock two locks while avoiding deadlock.
>
> Ah, that's what happens from looking at my not so good test case,
> rather
> than the docs :-)
>
> try_read_write_mutex does have member typedefs try_read_lock and
> try_write_lock, which do what you want them to, I just happened to use
> a
> different locking primitive because it was easier to change the test
> code
> around to test different combinations of readers and writers.
>
>> Sorry, but: Houston, we have an even bigger problem...
>
> Not this time, still it was big enough to begin with :-(

As a simple exercise, code this:

template <class TryLock1, class TryLock2>
void
lock(TryLock1& l1, TryLock2& l2);

Here is one possible implementation:

template <class TryLock1, class TryLock2>
void
lock(TryLock1& l1, TryLock2& l2)
{
     while (true)
     {
         l1.lock();
         if (l2.try_lock())
             break;
         l1.unlock();
         l2.lock();
         if (l1.try_lock())
             break;
         l2.unlock();
     }
}

Another possibility would be to assume an ordering functionality among
the locks:

template <class Lock1, class Lock2>
void
lock(Lock1& l1, Lock2& l2)
{
     if (l1 < l2)
     {
         l1.lock();
         l2.lock();
     }
     else
     {
         l2.lock();
         l1.lock();
     }
}

The former assumes the locks know how to lock(), try_lock() and
unlock(). The latter assumes the locks are less-than-comparable, and
know about lock(). Neither of these assume try_read_lock or
try_write_lock. Read locking and write locking are issues orthogonal
to this generic code. If this generic 2-lock function must know how to
read-lock and write-lock (and perhaps even promote from read to write
lock), things get significantly messier.

In the design I've laid out, the generic 2-lock algorithm can deal with
two exclusive locks, two read locks, two upgradable locks, or any
combination of the above (modulo the ordering functionality -- that's a
different soap box ;-) ). You could even atomically lock an exclusive
lock, while promoting another one from read to write using this same
generic code (using a promote_lock adaptor not yet presented). And if
you don't do something like the above, you are subject to deadlock if a
thread tries to hold one lock while promoting another.

A lock (read, scoped, whatever) is either locked or unlocked. You can
try-lock it or timed-lock it. But you can't read-lock it, write-lock
it or any other kind of lock. You can only lock it or unlock it. It
is the mutex that holds the read/write magic, not the lock:

template <class RW_Mutex>
class sharable_lock
{
public:
     ...
     void lock() {m_.lock_sharable(); locked_ = true;}
     ...
private:
     mutex_type& m_;
     bool locked_;
};

-Howard


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