
On Jul 16, 2004, at 1:21 AM, Matt Hurd wrote:
On Thu, 15 Jul 2004 22:30:37 -0400, Howard Hinnant <hinnant@twcny.rr.com> wrote:
On Jul 15, 2004, at 7:42 PM, Matt Hurd wrote:
The constructor for transfer_lock takes two locks, referencing the same mutex, the first of which must be unlocked, and the second locked.
In principle, tranferring the true from one bool to another sounds a nice thing to do, however:
I'm not sure this is a good idea. ... Do you have a specific use case in mind?
Yes, the upgradable_read_lock discussed in this thread. Sometimes you need to transfer ownership from an upgradable_read_lock to a write_lock, or vice-versa. Note, this is not transferring ownership among threads, but within one thread. The transfer_lock utility can help you do the transfer with the RAII idiom (do the transfer in transfer_lock's constructor, and reverse the transfer back in the destructor).
The constructor for lock_both takes two locks, referencing different mutexes, both of which must be unlocked.
Example use:
mutex m; scoped_lock lock1(m1, deferred); // or whatever syntax for not locking scoped_lock lock2(m2, deferred); lock_both<scoped_lock, scoped_lock> lock(lock1, lock2); // m1 and m2 atomically locked here, without fear of deadlock
Without the ability to construct but defer locking of lock1 and lock2, the construction of lock_both becomes inefficient and awkward.
My immediate reaction was to think this was a bad idea, but a moment later I'm not so sure.
My initial thought, and I think it is still valid, was that locking many things atomically doesn't make sense. You have to lock them one after the other and always in the same order to prevent deadlock. Perhaps you have a scheme in mind where you try lock for all things and release if you can't get all and then try again causing a group spin lock? I think I'm missing a part of your picture, let me know what you're thinking.
Consider: class A { typedef rw_mutex mutex; typedef mutex::read_lock read_lock; typedef mutex::write_lock write_lock; typedef lock_both<write_lock, read_lock> lock_both; public: ... A& operator = (const A& a); ... private: mutable mutex mut_; int i_; // just example data int j_; // just example data }; A& A::operator = (const A& a) { if (this != &a) { write_lock wl(mut_, false); read_lock rl(a.mut_, false); lock_both lk(wl, rl); i_ = a.i_; j_ = a.j_; } return *this; } The operator= must make sure the rhs is safe for reading, and the lhs is safe for writing. Since each object is protected with its own mutex, two mutexes must be locked. It is convenient to have lock_both take care of the details of how to atomically lock these two mutexes without deadlock, whether that is by ordering, or by try-and-back-off. As discussed earlier in this thread, the try-and-back-off algorithm will not lead to spin, and indeed may offer advantages over ordering. Whatever the algorithm, it is good to encapsulate it.
Thanks for the information. I take your point that lock is pretty much the same size regardless of the mutex. The mutex size variation does not concern me too much but I could see it will concern some, e.g. an embedded resource poor system or a design where you might go crazy and have a million mutexes which is something I'm thinking off as a space / time tradeoff.
Mutex size concerns me because it is common to put a mutex into an object (like A above). And one can easily have many A's (e.g. vector<A>). Locks otoh are usually locally declared stack-based objects. -Howard