Boost logo

Boost :

From: Howard Hinnant (hinnant_at_[hidden])
Date: 2004-07-16 11:01:22


On Jul 16, 2004, at 1:21 AM, Matt Hurd wrote:

> On Thu, 15 Jul 2004 22:30:37 -0400, Howard Hinnant
> <hinnant_at_[hidden]> wrote:
>> On Jul 15, 2004, at 7:42 PM, Matt Hurd wrote:
>>
>> The constructor for transfer_lock takes two locks, referencing the
>> same
>> mutex, the first of which must be unlocked, and the second locked.
>
> In principle, tranferring the true from one bool to another sounds a
> nice thing to do, however:
>
> I'm not sure this is a good idea. ... Do you have a
> specific use case in mind?

Yes, the upgradable_read_lock discussed in this thread. Sometimes you
need to transfer ownership from an upgradable_read_lock to a
write_lock, or vice-versa. Note, this is not transferring ownership
among threads, but within one thread. The transfer_lock utility can
help you do the transfer with the RAII idiom (do the transfer in
transfer_lock's constructor, and reverse the transfer back in the
destructor).

>> The
>> constructor for lock_both takes two locks, referencing different
>> mutexes, both of which must be unlocked.
>>
>> Example use:
>>
>> mutex m;
>> scoped_lock lock1(m1, deferred); // or whatever syntax for not
>> locking
>> scoped_lock lock2(m2, deferred);
>> lock_both<scoped_lock, scoped_lock> lock(lock1, lock2);
>> // m1 and m2 atomically locked here, without fear of deadlock
>>
>> Without the ability to construct but defer locking of lock1 and lock2,
>> the construction of lock_both becomes inefficient and awkward.
>
> My immediate reaction was to think this was a bad idea, but a moment
> later I'm not so sure.
>
> My initial thought, and I think it is still valid, was that locking
> many things atomically doesn't make sense. You have to lock them one
> after the other and always in the same order to prevent deadlock.
> Perhaps you have a scheme in mind where you try lock for all things
> and release if you can't get all and then try again causing a group
> spin lock? I think I'm missing a part of your picture, let me know
> what you're thinking.

Consider:

class A
{
     typedef rw_mutex mutex;
     typedef mutex::read_lock read_lock;
     typedef mutex::write_lock write_lock;
     typedef lock_both<write_lock, read_lock> lock_both;
public:
     ...
     A& operator = (const A& a);
     ...
private:
     mutable mutex mut_;
     int i_; // just example data
     int j_; // just example data
};

A&
A::operator = (const A& a)
{
     if (this != &a)
     {
         write_lock wl(mut_, false);
         read_lock rl(a.mut_, false);
         lock_both lk(wl, rl);
         i_ = a.i_;
         j_ = a.j_;
     }
     return *this;
}

The operator= must make sure the rhs is safe for reading, and the lhs
is safe for writing. Since each object is protected with its own
mutex, two mutexes must be locked.

It is convenient to have lock_both take care of the details of how to
atomically lock these two mutexes without deadlock, whether that is by
ordering, or by try-and-back-off. As discussed earlier in this thread,
the try-and-back-off algorithm will not lead to spin, and indeed may
offer advantages over ordering. Whatever the algorithm, it is good to
encapsulate it.

> Thanks for the information. I take your point that lock is pretty
> much the same size regardless of the mutex. The mutex size variation
> does not concern me too much but I could see it will concern some,
> e.g. an embedded resource poor system or a design where you might go
> crazy and have a million mutexes which is something I'm thinking off
> as a space / time tradeoff.

Mutex size concerns me because it is common to put a mutex into an
object (like A above). And one can easily have many A's (e.g.
vector<A>). Locks otoh are usually locally declared stack-based
objects.

-Howard


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