Boost logo

Boost :

From: Howard Hinnant (hinnant_at_[hidden])
Date: 2004-07-12 09:41:01


On Jul 11, 2004, at 8:46 AM, Peter Dimov wrote:

> Seems entirely reasonable. Perhaps it's time to move to formal wording.
>
> template<class M> class scoped_lock
> {
> public:
>
> explicit scoped_lock( M & m, bool l = true );
> // effects: if( l ) m.lock();
> // post: locked() == l && mutex() == &m;
>
> ~scoped_lock();
> // effects: if( locked() ) mutex()->unlock();
>
> void lock();
> // throws: lock_aready_locked if locked();
> // effects: mutex()->lock();
> // post: locked();
>
> bool try_lock();
> // throws: lock_aready_locked if locked();
> // returns: mutex()->try_lock();
> // post: locked() == (return value);
>
> bool timed_lock( xtime xt );
> // throws: lock_aready_locked if locked();
> // returns: mutex()->timed_lock( xt );
> // post: locked() == (return value);
>
> void unlock();
> // throws: lock_not_locked when !locked();
> // effects: mutex()->unlock();
> // post: !locked();
>
> bool locked() const;
>
> Mutex * mutex() const;
> // returns: the associated mutex;

Why return a pointer instead of a reference? What about this instead?

       Mutex& mutex();
       const Mutex& mutex() const;

That would put to rest any questions about mutex() transferring mutex
ownership, e.g.:

       delete lock.mutex();

>
> operator unspecified-bool-type() const;
> // returns: locked().
> };
>
> I've added the mutex() accessor to support Andrei's lock idiom:
>
> void f()
> {
> scoped_lock lock( my_mutex );
> f( lock );
> }
>
> void f( scoped_lock & lock )
> {
> // check for lock validity
> assert( lock.locked() && lock.mutex() == &my_mutex );
>
> // proceed with operation
> }
>
> Now that we got rid of the excess locks, how about doing the same with
> the
> mutexes?

That worries me a lot. We need several different flavors of mutex
because the more functionality you put into a mutex, the more expensive
it is both in terms of size and speed. Why pay for a recursive mutex
if you don't need one? Why pay for a timed mutex? But when you need
one of these more expensive types, then the price is worth it.

We can get away with a scoped/try/timed lock precisely because lumping
that functionality into these templated classes does not add overhead
(the overhead is in the mutex).

> I was somewhat surprised by the mutex/cv implementation. I expected a
> thin
> wrapper over pthreads, but this is not the case. At first sight it
> seems
> that the complexity is caused by the fact that boost::condition
> supports
> recursive mutexes that are locked more than once, whereas POSIX does
> not. I
> do not recall any discussions about this issue, and I'm not sure why
> this
> decision was made. It seems wrong (and the implementation seems
> buggy), but
> as usual, I may be missing something.

I can't speak for the boost implementation (haven't carefully studied
it), but the Metrowerks implementation also supports conditions
operating on a recursive mutex. Our implementation (and I strongly
suspect boost's as well) also supports conditions operating on
non-recursive mutexes. The condition::wait<NonRecursiveMutex> /is/ a
thin wrapper around pthread_cond_wait (when implemented on pthreads).
Overhead is only incurred for condtion::wait<RecursiveMutex>.

On the other hand, our Windows implementation of condition::wait
follows the algorithm laid out by Alexander Terekhov's "Algorithm 8a"
posted in comp.programming.threads on April 27, 2001 (and noted as such
in that file Alexander). In this case the wait() function does not
differ depending upon the recursiveness of the mutex.

This underscores the importance of having different types of mutexes.
Adding functionality to a mutex (beyond a non-recursive, non-try,
non-timed mutex) may well add significant overhead on some platforms,
whether or not you actually use that added functionality.

-Howard


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