|
Boost : |
From: Matt Hurd (matt.hurd_at_[hidden])
Date: 2005-04-28 13:50:14
On 4/29/05, Michael Glassford <glassfordm_at_[hidden]> wrote:
> I've been mentioning that I have some ideas about improvements for
> Boost.Threads. I intend to bring up these ideas one at a time for
> discussion. I'm particularly interested in finding out:
Kewl.
> 1) Whether people think that the idea would be an improvement.
> 2) If so, suggestions for improving the idea even more.
>
> The first idea has to do with the long discussion that occured some time
> back about the lock class--in particular, its constructors. There were
> several ideas about whether lock constructors should make the full range
> of lock, try_lock, and timed_lock functionality available and, if so,
> how; I don't recall any consensus being reached. I experimented for a
> while with a series of template classes that would allow a user to
> choose whatever interface they wanted, but another idea has occured to
> me since then that I don't remember being mentioned anywhere (if I'm
> wrong, my apologies to whoever mentioned it): to eliminate the locking
> contructors altogether. In this scheme, a lock object would no longer be
> responsible for locking a mutex, only for unlocking it. Instead, a mutex
> object would lock itself and transfer ownership of the lock to a lock
> object by way of a lock_transfer object.
>
> A sample of what the classes would look like and their usage is given at
> the end of this message.
>
> Some advantages I see to this approach:
>
> #1: it simplifies the lock class and allows it to be used to lock any
> costructor, no matter what its type (it can own a lock to an exclusive
> mutex, an exclusive lock to a shared_mutex, or a shared lock to a
> shared_mutex).
>
> #2: instead of the following, which in my mind leaves an ambiguity of
> whether the mutex should be unlocked, shared-locked, or exclusive-locked
> at the end:
>
> shared_lock l1(shared_mutex, SHARED_LOCK);
> ...
> {
> exclusive_lock l2 = l1.promote();
> ...
> }
>
> It is possible to write this, which removes the ambiguity:
>
> lock l = shared_mutex.shared_lock();
> ...
> l = shared_mutex.promote(l.transfer());
> ...
>
> Comments?
>
> Mike
>
Bad example I think. Personally, the risk of dead lock from trying to
promote shared to an exclusive lock strikes me as goto-esque. I
really struggle to find a compelling use case that makes the danger
involved worthwhile. I recall suggesting that if the dangerous
efficiency was really required it should be through a special
promotable shareable mutex rather than the normal shareable one.
I agree with your push to clean up the lock constructor interface. I
currently wrap them all and use my own interface so I can have generic
code for null, exclusive and shared ops.
Not sure about the lock transfer object as it is really just another
name for a lock. After all it is the token representing the locked
state, or a lock by another name... Perhaps just being able to return
a lock from a mutex and being able to transfer ownership by copying a
lock like an auto ptr would give you the same thing. I need to think
about this some more.
Importantly, I believe you need to be able to request a share lock
even of an exclusive mutex or null mutex so that you can write generic
concurrent code. You write to a shared model and the other models
work.
In the code below, you can see I simply added an extra parameter to
all the constructor with the lock status request to make it
orthogonal. With your approach you could have a lock transfer object
being able to be constructed from a shared_lock, exclusive_lock,
method. I'd want to see all mutexes with shared and exclusive lock
methods, including the null mutex that boost doesn't have, but I'm
not sure your suggested approach has an advantage.
matt.
_________________
I use this lock redirection:
template<class Mutex>
struct null_lock
{
null_lock( Mutex& m, lock_status::type ls = lock_status::exclusive ) {}
bool try_lock() {}
bool timed_lock(const boost::xtime &) {}
private:
null_lock (const null_lock&);
null_lock& operator= (const null_lock&);
};
template<class Mutex>
struct simple_lock
{
simple_lock( Mutex& m, lock_status::type ls =
lock_status::exclusive) : lock_(m) {}
bool try_lock() { return lock_.try_lock(); }
bool timed_lock(const boost::xtime &xt ) { return
timed_lock.timed_lock(xt); }
private:
simple_lock (const simple_lock&);
simple_lock& operator= (const simple_lock&);
private:
typename Mutex::scoped_lock lock_;
};
template<class Mutex>
struct shareable_lock
{
shareable_lock( Mutex& m, lock_status::type ls =
lock_status::exclusive) : lock_(m,convert(ls)) {}
bool try_lock() { return lock_.try_lock(); }
bool timed_lock(const boost::xtime &xt ) { return
timed_lock.timed_lock(xt); }
private:
shareable_lock (const shareable_lock&);
shareable_lock& operator= (const shareable_lock&);
private:
typename Mutex::scoped_read_write_lock lock_;
};
With these kind of type wrappers:
struct no_synch
{
struct nil_mutex
{
nil_mutex( lock_priority::type lp = lock_priority::exclusive ) {}
~nil_mutex() {}
};
typedef nil_mutex mutex;
typedef nil_mutex try_mutex;
typedef nil_mutex timed_mutex;
typedef null_lock<mutex> lock;
typedef null_lock<try_mutex> try_lock;
typedef null_lock<timed_mutex> timed_lock;
typedef atomic_op_simple atomic_op;
};
struct simple
{
protected:
struct adapted_mutex : public boost::mutex
{
adapted_mutex( lock_priority::type lp =
lock_priority::exclusive ) {}
~adapted_mutex() {}
};
struct adapted_try_mutex : public boost::try_mutex
{
adapted_try_mutex( lock_priority::type lp =
lock_priority::exclusive ) {}
~adapted_try_mutex() {}
};
struct adapted_timed_mutex : public boost::timed_mutex
{
adapted_timed_mutex( lock_priority::type lp =
lock_priority::exclusive ) {}
~adapted_timed_mutex() {}
};
public:
typedef adapted_mutex mutex;
typedef adapted_try_mutex try_mutex;
typedef adapted_timed_mutex timed_mutex;
typedef simple_lock<mutex> lock;
typedef simple_lock<try_mutex> try_lock;
typedef simple_lock<timed_mutex> timed_lock;
typedef atomic_op_interlocked atomic_op;
};
struct recursive
{
protected:
struct adapted_mutex : public boost::recursive_mutex
{
adapted_mutex( lock_priority::type lp =
lock_priority::exclusive ) {}
~adapted_mutex() {}
};
struct adapted_try_mutex : public boost::recursive_try_mutex
{
adapted_try_mutex( lock_priority::type lp =
lock_priority::exclusive ) {}
~adapted_try_mutex() {}
};
struct adapted_timed_mutex : public boost::recursive_timed_mutex
{
adapted_timed_mutex( lock_priority::type lp =
lock_priority::exclusive ) {}
~adapted_timed_mutex() {}
};
public:
typedef adapted_mutex mutex;
typedef adapted_try_mutex try_mutex;
typedef adapted_timed_mutex timed_mutex;
typedef simple_lock<mutex> lock;
typedef simple_lock<try_mutex> try_lock;
typedef simple_lock<timed_mutex> timed_lock;
typedef atomic_op_interlocked atomic_op;
};
struct shareable
{
protected:
struct adapted_mutex : public boost::read_write_mutex
{
adapted_mutex(
lock_priority::type lp = lock_priority::exclusive
)
: read_write_mutex( convert( lp) ) {}
~adapted_mutex() {}
};
struct adapted_try_mutex : public boost::try_read_write_mutex
{
adapted_try_mutex(
lock_priority::type lp = lock_priority::exclusive
)
: try_read_write_mutex( convert( lp) ) {}
~adapted_try_mutex() {}
};
struct adapted_timed_mutex : public boost::timed_read_write_mutex
{
adapted_timed_mutex(
lock_priority::type lp = lock_priority::exclusive
)
: timed_read_write_mutex( convert( lp) ) {}
~adapted_timed_mutex() {}
};
public:
typedef adapted_mutex mutex;
typedef adapted_try_mutex try_mutex;
typedef adapted_timed_mutex timed_mutex;
typedef shareable_lock<mutex> lock;
typedef shareable_lock<try_mutex> try_lock;
typedef shareable_lock<timed_mutex> timed_lock;
typedef atomic_op_interlocked atomic_op;
};
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk