Boost logo

Boost :

From: Moore, Dave (dmoore_at_[hidden])
Date: 2002-05-17 09:00:53


Bill,
 
As an observation, your mentioning of "auto_ptr" has many people thinking
you're talking about thread-safe smart/shared pointers.
 
I agree with your idea the transferring ownership of a lock object is quite
similar to the problem faced by std::auto_ptr.
 
It would solve a problem I came across in my first rw_mutex implementation
(not the one currently under CVS). I needed to hold a lock across method
invokations, but still wanted the ScopedLock protection for the first
several operations in the function. Once everything went well, I needed to
"disengage" the ScopedLock. I was then relying on calling lock_ops::unlock
later, which was unsafe.
 
If I could have transferred the lock out to another object, it would have
solved my problem more elegantly and with more safety. I like it. It also
lets you keep ScopedLocks as "mandatory", which is another design principle
I agree with.
 
Regards,
Dave
 

-----Original Message-----
From: William E. Kempf [mailto:williamkempf_at_[hidden]]
Sent: Thursday, May 16, 2002 12:06 PM
To: Boost
Subject: [boost] Boost.Threads Locking Delima

I've started the development for the next version of Boost.Threads recently
and have been pondering many of the issues that people have with the current
design. This post is about one of the problems cited by users on various
forums, specifically the need for a non-scoped locking mechanism. I'm going
to lay a lot of ground work for anyone that doesn't know the history of the
problem. If you don't care about this ground work you can skip ahead to the
paragraph that starts "RFC:".
 
I still lean towards thinking that it's wrong to add public
lock()/unlock()/etc. methods to the mutex classes because direct use of such
operations is the most common source of error in the code I've had to deal
with. (Granted, the ScopedLock pattern doesn't solve all issues such as the
need to keep the shared data in a consistent state as well as the lock, but
it's a very good step forward and reduces many bugs.) There are basically
two cases where the ScopedLock pattern causes problems for developers and is
why many wish I'd simply add the locking methods to the mutex classes. Here
they are with pseudo-code examples:
 
1) The need for "overlapping lock operations", sometimes used for
synchronizing "chain" operations such as linked list traversals.
 
m1.lock();
do_something_to_m1_shared_data();
m2.lock();
do_something_to_m1_and_m2_shared_data();
m1.unlock();
do_something_to_m2_shared_data();
m2.unlock();
 
2) The need for locking in one "scope" and ulocking in another "scope".
The best example is in an implementation of the locking_ptr<> class where a
proxy class is used to make operator-> protect the pointed to object with a
mutex, locking it before access and unlocking it afterwards. Multiple
"scopes" occur because the proxy must be returned by
locking_ptr<>::operator-> which necessitates locking in
locking_ptr<>::operator-> (first "scope") and unlocking in proxy::~proxy()
(second "scope"). For a detailed paper on the technique see
http://www.research.att.com/~bs/wrapper.pdf
<http://www.research.att.com/~bs/wrapper.pdf> .
 
The first of these problems is solved by the ScopedLock concepts in
Boost.Threads by including public interfaces for the lock operations on the
ScopedLock instead of on the Mutex. We're still insured the Mutex will be
unlocked but can now easily overlap multiple locks (as well as other
advanced locking schemes).
 
It's the second problem that's more difficult. I originally thought I'd
solve this solution by creating some sort of lock_operations<> template that
would expose a Mutex's lock operations externally, thus reducing the
likelyhood that a user would abuse the use of the lock operations solely
because they were readily available in the Mutex's interface. However, this
isn't really a great solution. For one thing it just feels like a hack, and
the end result is still an interface that's easily misused.
 
Now that I've laid the ground work (a lot of it... sorry to bore anyone who
already knew all of this), here's the crux of the posting:
 
RFC:
 
One night while trying to fall asleep my mind drifted to this problem (yeah,
I know what that says about me), and it dawned on me that the standard
already has a solution to a very similar problem. The std::auto_ptr<>
template solves a similar issue through "ownership" and "move semantics"
instead of the more traditional copy semantics. This allows you to pass a
std::auto_ptr<> out of one "scope" and into another, passing the ownership
and insuring only one "ptr" will ever delete the object. I could apply the
same technique to the ScopedLock concepts and allow the lock "ownership" to
be exclusive and transferrable. In many ways this seems like a much better
solution then a lock_operations<> template. However, there are issues with
"move semantics" that have caused a lot of questions about how to use
std::auto_ptr<> correctly, and these issues would exist here as well. Also,
I'm not 100% sure this solution will cover all use cases where the
ScopedLock is problematic. So what I'm looking for is comments on whether
or not this solution is viable and/or the solution I should implement.
Thoughts anyone?
 
Bill Kempf



Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk