Boost logo

Boost :

From: vicente.botet (vicente.botet_at_[hidden])
Date: 2008-03-18 19:14:49


Hi,

From: "klaus triendl" <klaus_at_triendl.eu>
> vicente.botet schrieb:
[snip]
>> I'm interested in your implementation, but much more in seen how this
>> lock_acquirer differs from the locking_ptr and in seen how it can works
>> with
>> the shared mutex.
>> Could you be more explicit about how it is more threadsafe?
>>
>> There are other locking pointer like inspired also by Andrei
>> Alexandrescu's
>> work that will be interesting as
>> on_derreference_locking_ptr(on_derreference_lock_acquirer) and
>> externally_locked.
>
> Hi Vicente,
>
> I expressed myself too vague - the lock_acquirer itself isn't more
> threadsafe than a locking_ptr but the way the programmer has to use it
> leads to more threadsafe programming behaviour, I believe. Everything
> depends on the usage scenario, of course.
>
> A locking_ptr takes the lockable (locker, or whatever you call it) and
> provides access via operator ->() and operator *(); thus you can write:
> <code>
> lockable<T, mutex> l;
> T* p = locking_ptr<lockable<T, mutex> >(l).operator ->();
> T& o = *locking_ptr<lockable<T, mutex> >(l);
> </code>
>
> ... constructing the locking_ptr and accessing an object in a single line.
>
> lock_acquirer doesn't aim to be a smart pointer (though I like also the
> idea of having an automatic locking while accessing an object's method).
> It is constructed from a lockable but access to the object is only
> granted via a protected friend template function forcing to pass a named
> lock_acquirer object:
> <code>
> lockable<T, mutex> l;
> lock_acquirer<lockable<T, mutex> > a(l);
> T& o = access_acquiree(a);
> </code>
>

In (*) below, is the mutex locked or not? If yes, why do we need
access_acquiree(a)? If not when the mutex is locked?

<code>
lockable<T, mutex> l;
lock_acquirer<lockable<T, mutex> > a(l);
//(*)
T& o = access_acquiree(a);
//(**)
</code>

I supose that it is not possible to unlock l in (**).

Your design recall the one of externally_locked class.

externally_locked cloaks an object of type T and a reference of a Lockable
objet,
used to synchronize this object and possibily others. Actually provides full
access to that object through the get member function (set could also be
considered), provided you pass a reference to a Locker object that owns the
Lockable.

Here it is my implementation:

<code>
template <typename T, typename Lockable>
class externally_locked {
public:
  externally_locked(T& obj, Lockable& lockable)
    : obj_(obj)
    , lockable_(lockable)
    {}

  template <class Locker>
  T& get(Locker& locker) {
    BOOST_STATIC_ASSERT(is_strict_locker<Locker>);
    if (locker) {
        Locker try_lock(lockable_, locking_traits<Lockable>::try_to_lock());
        if (!try_lock) {
            return obj_;
        } else {
            try_lock.unlock();
            throw bad_lock();
        }
    } else {
        throw bad_lock();
    }
 }
 /// .... other functions
private:
  T& obj_;
  Lockable& lockable;
};
</code>

And used as follows
<code>
T t;
boost::mutex m;
externally_locked<T, boost::mutex> el_t(t, m);
{
   boost::lock_guard<mutex> g(l);
   ...

   /// from here can use el_t.get(g). to access 't' in a thread safe mode.
   el_t.get(g).fct();
}
</code>

is_strict_locker is true if the locker is unable to unlock the lockable
instance and locking_traits provides some traits associated to the locable
concept.

>
> An additional bonus of lock_acquirer is that the programmer can specify
> a locking policy (read/write) as a template parameter and the
> lock_acquirer selects an appropriate r/w lock for the given mutex type.
> If the locking policy is read access then lock_acquirer grants
> const-access only to the synchronized object even if T in lockable<T> is
> non-const:
> <code>
> lockable<int, mutex> l;
> // readlock_acquirer derives from
> // lock_acquirer<readlock, lockable<int, mutex> >
> readlock_acquirer<lockable<int, mutex> > a(l);
> const int& o = access_acquiree(a);
> </code>

Which is the type of access_acquiree parameter?
Have you un implementation using the boost shared_mutex, ?

>
>>> Also, the locked type in a lockable can, if wanted, by a restricted
>>> interface only be accessed by a lock_acquirer thus forcing the
>>> programmer to a very threadsafe programming style.
>>
>> Could you show us how?
>
> The lockable type has public access methods, a safe_lockable however
> makes lock_acquirer a friend:
>
> template<typename T, typename T_mutex>
> struct safe_lockable: public lockable_base<T_mutex>
> {
> template<...> friend class lock_acquirer;
>
> protected:
> volatile_T& access_volatile();
> T& access_nonvolatile();
> };

Shouldn't safe_lockable use private or protected inheritance?

This recalls me the backdoor pattern already used in the preceding version
of the threads library. I really liked how the mutex were designed. It was
really safe. No possibility to lock without unlock. The single problem was
that the backdoor class was not provided as part of the interface, it was on
the detail namespace.
This two level interface, safe interface through the public interface and an
unsafe one using a backdoor seams to me very promising. IMHO it was a pitie
that the thread proposal for C++0x has removed this. I suppose that they had
good raisons to remove it.

Here it is the design of a mutex and a lock_guard with a backdoor.

template <class Lockable>
struct lockable_backdoor {
    lockable_backdoor(Lockable&);
    lock();
    unlock();
    Lockable lock_;
};

class mutex : boost::noncopyable{
public:
    // no safe interface other than using guards as lock_guard, ...

private:
    template <typename> friend class lockable_backdoor;
    // ...
};

template<typename Lockable>
class lock_guard
{
public:
    explicit lock_guard(Lockable& m): m_(m){
        lockable_backdoor(m_).lock();
    };
    ~lock_guard() {
        lockable_backdoor(m_).lock();
    }
};

Best regards

Vicente


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