Boost logo

Boost :

From: Arlo Belshee (arlo_at_[hidden])
Date: 2002-06-13 10:47:41


I'm a little behind on the list, so please ignore this posing if someone
else has posted it in the last month.

> -----Original Message-----
> From: boost-bounces_at_[hidden]
> Subject: Re: [boost] Boost.Threads Locking Delima
>
> The ability to lock and unlock in different scopes is required to
> implement a lockingptr<>.

While this is true, I think there is still an implementation for
lockingptr<> that uses only the currently existing ScopedLock interface.
This is a relatively straightforward modification of the reference counting
for proxies pattern that I believe solves the problem.

template<>
class lockingptr_proxy
{
        //operations
        ...
        lockingptr_proxy(ScopedLock *lock)
                : m_Lock(lock) {}
        ~lockingptr_proxy() {
                delete m_Lock; }
private:
        ScopedLock *m_Lock;
};

template<>
class lockingptr
{
...
        lockingptr_proxy& operator*() {
                return lockingptr_proxy(new ScopedLock(m_Mutex)); }
private:
        Mutex m_Mutex;
};

Catch me if I'm wrong, but doesn't this solve the problem? The lock will be
acquired upon dereference (creation of proxy), and will be released when the
temporary is cleaned up. Even in case of exceptions, the temporary will be
cleaned up, and it will take the ScopedLock off of the heap in its
destructor.

If I'm not missing anything, then perhaps the best approach is not to alter
the interface of ScopedLock at all, but to instead simply provide a
lockingptr<> implementation in the library, so that other people don't have
to resolve the wheel.

However, this only answers half of the reason for altering the interface.
There was also a desire to allow passing the lock into functions, presumably
so that they could conditionally decide to free the lock, while guaranteeing
that the lock being freed when the caller goes out of scope in any case.

This, it seems, could be solved in a manner similar to how one solves the
lock chaining problem. Pass in the ScopedLock by reference, and if the
callee wishes to unlock, she may via unlock(). If the callee does not
unlock, the lock will be freed when the caller goes out of scope.

The only remaining case is when a function wants to create a ScopedLock and
pass it back to its parent, yet still guarantee that it eventually gets
cleaned up.

That problem is a little more annoying, and could be cited as a reason to
allow generic lock() and unlock() on a mutex. However, I think that a
cleaner solution would be to allow lock to have a default constructor and a
reassign(m) function. Reassign would lock on the new mutex, then release the
old mutex (allowing chaining, but giving the possibility of deadlock -
alternate implementations may be made available). With those two changes,
the following code allows safe return of a lock up the stack:

function called(ScopedLock &lock, ...)
{
        lock.reset(m_Mutex);
        ...
}

function caller()
{
        ...
        ScopedLock _lock;
        called(_lock);
        // we are still locked on m_Mutex, and will release when _lock goes out of
scope.
}

Given these three solutions, are there still remaining problems that require
mutex to expose lock() and unlock() functions? Or, are there problems with
my solutions that leave cases still uncovered? If the answer to both
questions is no, then perhaps we should not expose the two generic
functions, and instead include lockingptr<> in the library & document how to
pass ScopedLocks safely into and out of functions as examples in the
documentation.

Arlo Belshee


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