Boost logo

Boost :

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


On Jul 16, 2004, at 1:41 PM, Christopher Currie wrote:

> I'm just going to be the voice of dissent here, again, and argue that
> the scoped_locks, at least, should not be moveable, otherwise they're
> not very scoped. The scoped lock serves a specific purpose in ensuring
> that the mutex doesn't remain locked outside of block scope, just like
> scoped_ptr ensures that a heap object has a specific lifetime.

The first practical application of move semantics to locks that I saw
was when I started playing around with read/write/upgradable locks.
The very idea of an upgradable_read_lock is that it can transfer
ownership to a write_lock. And if you cast this operation in the move
syntax suggested by n1377 you can get some fairly elegant code (imho).
Consider a class A with a rw_mutex as a member:

class A
{
     typedef Metrowerks::rw_mutex mutex;
     typedef mutex::read_lock read_lock;
     typedef mutex::write_lock write_lock;
     typedef mutex::upgradable_read_lock upgradable_read_lock;
public:
     ...
     void read_write_one_way();
     void read_write_another_way();
     ...
private:
     mutable mutex mut_;
};

There are two public functions (among others), that both read and
write. The rw_mutex must be locked accordingly. For convenience, it
would be nice if they could implement themselves in terms of a common
private read_impl(). Something like:

void
A::read_write_one_way()
{
     read_impl();
     write_lock wl(mut_);
     // do the write
}

This doesn't quite work because you don't want to free the read_lock
under read_impl() until you've obtained the write_lock in
read_write_one_way. You could:

class A
{
     typedef Metrowerks::rw_mutex mutex;
     ...
public:
     ...
     void read_write_one_way();
     void read_write_another_way();
     ...
private:
     mutable mutex mut_;

     void read_impl() const;
};

void
A::read_impl() const
{
     // precondition: client has at least read-locked
     // do the read
}

void
A::read_write_one_way()
{
    upgradable_read_lock url(mut_);
    read_impl();
    write_lock wl(url); // transfer ownership
    // do the write
}

void
A::read_write_another_way()
{
    upgradable_read_lock url(mut_);
    read_impl();
    write_lock wl(url); // transfer ownership
    // do the write
}

But if we really had move semantics as in n1377, I'd be tempted to:

class A
{
     typedef Metrowerks::rw_mutex mutex;
     ...
public:
     ...
     void read_write_one_way();
     void read_write_another_way();
     ...
private:
     mutable mutex mut_;

     upgradable_read_lock read_impl() const;
};

A::upgradable_read_lock
A::read_impl() const
{
     // process some stuff before needing to read-lock
     upgradable_read_lock url(mut_);
     // do the read
     return url;
}

void
A::read_write_one_way()
{
    write_lock wl(read_impl()); // transfer ownership
    // do the write
}

void
A::read_write_another_way()
{
    write_lock wl(read_impl()); // transfer ownership
    // do the write
}

The functionality is the same with either coding, but in the latter I
can put the read-locking logic where it belongs (in read_impl) instead
of depending upon each read_write function to correctly do the
read-locking. It also allows the possibility that read_impl() may not
need to read-lock right at the top of its function, but perhaps part
way through, resulting in higher concurrency (the less locked the
better).

<shrug> I don't see this as a huge win. But it does seem like a
reasonable application for moving locks. If you were to tell me this
scenario would be pretty rare, I'd have to agree with you. I imagine
90% of all lock use would be a simple scoped_lock (no try, no timed, no
recursive, no read/write).

-Howard


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