
On Aug 26, 2007, at 10:20 AM, David Abrahams wrote:
on Fri Aug 24 2007, Howard Hinnant <howard.hinnant-AT-gmail.com> wrote:
2. I think the concept of unique_lock is too fuzzy. I know what unique_ptr (and auto_ptr, and shared_ptr, and scoped_ptr) mean. With unique_lock, I can't quite tell. This might ultimately end up being fixed by a naming change, but I think there's an underlying conceptual problem, or at the very least, a missing rationale.
I will try to better fill out the rationale. And am certainly open to a name change.
In a nutshell, the current unique_lock<Mutex> is an evolution of the current boost::detail::thread::scoped_lock<Mutex>. It has been taken out of namespace detail, renamed, given move semantics, and slightly more flexibility in how it handles the mutex. The only invariant that has changed from the boost design is that one can have a unique_lock which doesn't reference a mutex. This was a consequence of a moved- from unique_lock.
That's not the rationale I'm looking for. I don't mean "how did we get here?" I mean, what *is* this thing, conceptually?
Conceptually a unique_lock is the sole RAII owner of the exclusive lock state of an object that meets certain Mutex concepts. At a minimum, the Mutex must support: void unlock(); Otherwise the unique_lock can not be destructed. If the unique_lock<Mutex>(Mutex&) constructor is instantiated, or if unique_lock<Mutex>::lock() is instantiated, then Mutex must support: void lock(); If the unique_lock<Mutex>(Mutex&, try_lock_type) constructor is instantiated, or if unique_lock<Mutex>::try_lock() is instantiated, then Mutex must support: bool try_lock(); If the unique_lock<Mutex>(Mutex&, nanoseconds) constructor is instantiated, or if unique_lock<Mutex>::timed_lock(nanoseconds) is instantiated, then Mutex must support: bool timed_lock(nanoseconds); If the unique_lock<Mutex>(tr2::upgrade_lock<Mutex>&&) constructor is instantiated, then Mutex must support: void unlock_upgrade_and_lock(); If the unique_lock<Mutex>(tr2::upgrade_lock<Mutex>&&, try_lock_type) constructor is instantiated, then Mutex must support: bool try_unlock_upgrade_and_lock(); If the unique_lock<Mutex>(tr2::upgrade_lock<Mutex>&&, nanoseconds) constructor is instantiated, then Mutex must support: bool timed_unlock_upgrade_and_lock(nanoseconds); If the unique_lock<Mutex>(tr2::shared_lock<Mutex>&&, try_lock_type) constructor is instantiated, then Mutex must support: bool try_unlock_shared_and_lock(); If the unique_lock<Mutex>(tr2::shared_lock<Mutex>&&, nanoseconds) constructor is instantiated, then Mutex must support: bool timed_unlock_shared_and_lock(nanoseconds);
What can I understand about a function that accepts one as an argument (for unique_ptr it's very clear, and exceedingly so for scoped_lock, since you can't do that)?
To accept a unique_lock (which currently owns the exclusive state) by value in a function parameter means transferring the ownership of that exclusive lock state from the current scope, to the scope within the called function.
What does it mean to return one from a function or store it in a container?
It means to transfer the sole ownership of the exclusive lock state from within the function, to the calling scope.
I understand what scoped_lock means. If unique_lock doesn't mean "no ownership or sole ownership of the mutex it references" then what does it mean, and what's the justification for calling it "unique?"
unique_lock means "no ownership or sole ownership of the mutex it references". I think your concern began with my std::lock example: On Aug 22, 2007, at 10:36 AM, David Abrahams wrote:
on Tue Aug 21 2007, Howard Hinnant <howard.hinnant-AT-gmail.com> wrote:
This line:
unique_lock<_L1> __u1(__l1);
implicitly calls __.l1.lock() inside of the unique_lock constructor. If __l1 is a mutex, the deed is done. If __l1 is a lock, hopefully that will forward to the referenced mutex's lock() function in the proper manner. And in the process, that should set the lock's owns() data to true as well.
That's part of what I found counfounding about the name "unique_." Now you have two locks (__l1 and __u1) that "own" the mutex.
__u1 now uniquely owns the exclusive lock state of __l1. __u1 does not know or care what it means for __l1 to be in an exclusive locked state. It only knows that it uniquely owns it. That is, until further notice, no other object in the program has executed __l1.lock(), or __l1.try_lock() (or any of the other functions which can put a mutex into a exclusive locked state) without having already executed __l1.unlock(). Anything that happens inside of __l1 when __l1 is in its exclusively locked state is an implementation detail of __l1, which __u1 is not privy to. -Howard