|
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