
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