
Hi, From: "klaus triendl" <klaus@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