
On 4/29/05, Michael Glassford <glassfordm@hotmail.com> 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; };