|
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