From: William E. Kempf (williamkempf_at_[hidden])
Date: 2002-05-16 11:05:45
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.
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.
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:
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?
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk