Subj : Re: Improving read-write lock To : comp.programming.threads From : Howard Hinnant Date : Thu Feb 17 2005 10:03 pm In article , "Joe Seigh" wrote: > On Thu, 17 Feb 2005 19:22:18 GMT, Howard Hinnant > wrote: > > > > > The part of this interface dealing with move semantics may have > > significant advantages in the not too far future such as the ability to > > return locks from functions (even locked locks), and the ability to put > > locks into standard containers without the fear of accidently > > transferring or leaking mutex ownership. > > I'm not even sure what moving a lock means, much less what the advantages > would be. In this interface (as well as the boost::threads interface it is based on), scoped_lock is a non-copyable object which holds a reference to a mutex. The scoped_lock is responsible for locking the mutex upon construction and unlocking it on destruction. A movable lock honors the non-copyability of this interface, with a couple of exceptions. You can "copy" an rvalue scoped_lock to another. As the rvalue scoped_lock is about to be destructed anyway, mutex ownership is transferred from the source to the target. As the source destructs, it no longer is obligated to unlock the mutex. Instead that responsibility is now the target's. For example: class A { public: ... scoped_lock lock() {return scoped_lock(mut_);} ... private: mutex mut_; }; .... A a; void foo() { // here "a.mut_" is unlocked { scoped_lock a_lock = a.lock(); // here "a.mut_" is locked } // here "a.mut_" is unlocked } In this example a local scoped_lock is created internal to A::lock(). That local scoped_lock is moved (not copied) to the client's a_lock, transferring ownership of mut_ as it goes. The scope of a_lock is effectively the scope of the lock on a.mut_. A::lock() instead could have returned a reference to mut_, but that may expose A's internals more than the author of A is comfortable with. A::lock() instead could lock the mut_. But then you need a A::unlock() which might forget to be called (especially if an exception is thrown somewhere after the lock). So returning (via move semantics) a scoped_lock from a function may provide some encapsulation benefits. There may be some (admittedly rare) circumstances it may be beneficial to transfer an existing scoped_lock's mutex ownership out of the current scope. This can also be accomplished with the "move" function: mutex mut; scoped_lock global; void foo() { scoped_lock local(mut); ... if (some condition) global = std::move(local); // global now owns mut } There may be some applications that benefit from an array of scoped_locks, and the array size may not be determined until run time (perhaps a database with an array of locks where each lock controls only a limited range in the database). A move-aware std::vector could hold moveable locks, without risk of accidental transfer of mutex ownership. Standard algorithms could help manipulate those locks. For example: std::vector > locks; struct is_not_locked { bool operator()(const scoped_lock& l) {return !l.locked();} }; foo() { locks.erase( std::remove_if(locks.begin(), locks.end(), is_not_locked()), locks.end()); } Here foo() uses std::remove_if and std::vector to destruct all locks which are currently not locked in the vector. vector will use the scoped_lock's movability to manage its internal buffer as the vector grows, sometimes needing to move a scoped_lock from an existing buffer to a larger buffer. remove_if will use the scoped_lock's movability to rearrange the vector such that all currently locked scoped_locks are moved to the front of the vector (such an operation would naturally need further synchronization). I'm currently exploring applications where moving locks may be beneficial. The above is some of the capability that moving locks gives you. I am interested in feedback as to whether any of this capability might prove useful. -Howard .