Boost logo

Boost :

From: William E. Kempf (williamkempf_at_[hidden])
Date: 2002-06-13 14:10:49


----- Original Message -----
From: "Arlo Belshee" <arlo_at_[hidden]>
To: <boost_at_[hidden]>
Sent: Thursday, June 13, 2002 10:47 AM
Subject: FW: [boost] Boost.Threads Locking Delima

> 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)); }

This is the issue. Dynamic memory allocations are expensive, and requiring
this on every use of operator-> is just not going to be acceptable.

> 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.

It performs the desired actions, but not in an efficient manner. Further,
it adds new complexity to code. It's going to be surprising to many users
that p->foo(); can throw std::bad_alloc when foo() does no memory
allocations.

> 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.

The lockingptr will usually provide the wrong granularity.

> 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.

Actually, the lock would be freed after the call to the function in which
you passed the lock. It's a transfer of ownership thing.

> 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.
> }

I don't see a lock return here, which is what you were trying to solve.
Also, if I follow what you're saying, there are two mutexes involved here,
when only one mutex should be. The problem isn't with chaining locks to
multiple mutexes, but with transferring a lock on a single mutex.

> 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.

I still don't see a way to "pass ScopedLocks safely into and out of
functions". Into sort of has an answer... pass by reference. But this
isn't a transfer of ownership. In any event, returning locks is still not
possible.

Bill Kempf


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