|
Boost : |
From: Talbot, George (Gtalbot_at_[hidden])
Date: 2008-07-23 10:29:13
Hi!
Thanks for responding.
> -----Original Message-----
> From: boost-bounces_at_[hidden]
[mailto:boost-bounces_at_[hidden]]
> On Behalf Of Dean Michael Berris
> Sent: Tuesday, July 22, 2008 9:24 PM
> To: boost_at_[hidden]
> Subject: Re: [boost] Would anybody like a wrapper atomic_shared<T>
> aroundshared_ptr<T>?
>
> Hi George,
>
> On Wed, Jul 23, 2008 at 6:04 AM, Talbot, George
<Gtalbot_at_[hidden]>
> wrote:
> >
> > A while back (~2006 I think) I was asking around on this list about
> > using boost::shared_ptr<T> to build a composite data structure that
can
> > be accessed and modified by multiple threads.
> >
> > Peter Dimov's suggestion at the time was to write a wrapper class
for
> > boost::shared_ptr<T> that would protect access to the
> > boost::shared_ptr<T> with a spinlock. I've done this, and this
works
> > pretty good in my application. I'm interested in contributing this
code
> > to BOOST, as it's been quite useful.
> >
> > I have a few questions:
> >
> > 1) Is there interest in such a donation. It's just one header
file,
> > and it's wafer-thin...
>
> I am interested, although I do have some reservations with it not
> using the locks already available in Boost.Thread.
I would likely convert it to do so. Haven't got around to that yet.
> > 2) If so, how does one go about making such a donation?
>
> (I think it's a little funny that you use 'donation' instead of
> contribution... but nonetheless... ;-) )
Sorry....contribution. I'm a bit of a space cadet. ;^)
> >From what I gather, this is something that may have to go through a
> mini-review. The veterans can answer this question better though.
I'm sure, and that would be great, actually.
> > 3) My implementation below uses a spinlock to protect things and
> > as one might expect, under load my application does spend some
> > time in ::pthread_spin_lock(). Is there a way to rewrite this
> > class (possibly with friendship of boost::shared_ptr<T>) in a
> > lock-free manner? I've attached the code below...
> >
>
> I'm not sure about lock-free approaches, but doesn't shared_ptr<T>
> already use atomic operations when built in multi-threaded mode?
Yes, but read the Thread Safety page. The guarantee you get from
shared_ptr<T> is that two or more separate instances in separate threads
that point to the same object and refcount will be handled lock-free and
work properly. However it explicitly does not guarantee multiple
threads looking at the _same_ instance of shared_ptr<T> can perform
operations upon that same instance without some sort of additional
guarantees. The purpose of this class is to provide that guarantee.
> Another issue I see is the dependence to ::pthread_spin_lock() -- can
> you use a shared recursive_mutex instead?
How expensive is that in memory and time? I was using
::pthread_spin_lock() because on my x86_64 Linux box, pthread_spinlock_t
is a single machine word.
Also, it's just a proof-of-concept. I think the "right thing" is to be
able to implement the unary bool operators, get() and compare_and_set()
via a lock-free algorithm. My problem is that I don't have enough depth
of knowledge of the lock-free shared_ptr<T> class in BOOST to be able to
work out such an algorithm. I was hoping for a little bit of "pointing
in the right direction" from the author(s) of BOOST's lock-free
shared_ptr<T> implementation.
That said I'd have no problem "falling-back" to using a shared
recursive_mutex instead if a lock-free algorithm isn't available on a
particular platform. As such, attached below is a rewrite using
recursive_try_mutex...This appears similar in execution efficiency to my
previous implementation with the spinlock in my stress test. It appears
from examining the pthread libraries that the mutex is using about 8
machine words instead of one for the spinlock.
Of course, as above, I'd prefer no lock at all; I'll see what I can come
up with.
Thanks for looking at this.
-- George T. Talbot gtalbot_at_[hidden] /** \file atomic_shared.h * * \author George T. Talbot * \author Peter Dimov * * \brief This file defines a template class that can wrap * boost::shared_ptr and provide thread-safe atomic * get/set operations on them. * * (C) Copyright Locus Pharmaceuticals, Inc., Peter Dimov 2006. * * Distributed under the Boost Software License, Version 1.0. (See * accompanying file LICENSE_1_0.txt or copy at * http://www.boost.org/LICENSE_1_0.txt) */ #ifndef ATOMIC_SHARED_H #define ATOMIC_SHARED_H #include <boost/shared_ptr.hpp> #include <boost/pointer_cast.hpp> #include <boost/thread/recursive_mutex.hpp> /** atomic_shared<T> wraps boost::shared_ptr<T> with a mutex and a very * restricted set of operations so that a large composite data structure * may be accessed by multiple threads with a minimum of locking contention. * * Threads working on a large composite data structure create their own * private boost::shared_ptr<T> around their own new nodes and then use * compare_and_set() to place their new nodes in the composite data * structure. * * Threads that are viewing the composite data structure may call get() to * get a private copy of the pointer so they can do some work with it. The * intended idiom for modifications to nodes is like so: * * \code * * atomic_shared<const Blah> global_blah; * * void modify_the_global_blah_in_many_threads_with_no_locking() * { * bool done = false; * do * { * boost::shared_ptr<const Blah> my_blah(global_blah.get()); * * boost::shared_ptr<Blah> new_blah(*my_blah); * new_blah->some_modification(); * done = global_blah.compare_and_set(my_blah, new_blah); * } * while (!done); * } * * \endcode * * Threads that just need to do some work looking at the node: * * \code * * void print_the_global_blah_in_many_threads_with_no_locking() * { * boost::shared_ptr<const Blah> my_blah(global_blah.get()); * * my_blah->print(); * } * * \endcode * * Using atomic_shared<T> this way satisfies the Thread Safety portion of * the boost::shared_ptr<T> documentation. */ template<class T> class atomic_shared { atomic_shared& operator=(const atomic_shared&); public: typedef boost::shared_ptr<T> base_ptr; /** construct an empty pointer. */ atomic_shared() : ptr(), mutex() { } /** copy constructor. Since we like to put atomic_shared<> in std::map<> * via std::map<atomic_shared<T> >::insert(std::make_pair(key, value)), * this needs to be here. */ atomic_shared(const atomic_shared& p) : ptr(p.get()), mutex() { } /** construct a protected pointer from a bare one. */ template<class Y> explicit atomic_shared(Y* p) : ptr(p), mutex() { } /** construct a protected pointer from a shared_ptr. */ template<class U> explicit atomic_shared(const boost::shared_ptr<U>& p) : ptr(p), mutex() { } ~atomic_shared() { } /** Is it not null? */ operator bool() const { bool rv; { boost::recursive_try_mutex::scoped_lock l(mutex); rv = ptr; } return rv; } /** Is it null? */ bool operator!() const { bool rv; { boost::recursive_try_mutex::scoped_lock l(mutex); rv = !ptr; } return rv; } /** Right here is the only way to _use_ the pointer. To use what the * pointer is pointing to, you must make a private copy in your * thread. */ inline base_ptr get() const { base_ptr p; { boost::recursive_try_mutex::scoped_lock l(mutex); p = ptr; } return p; } /** Attempt to set the value of the pointer with another pointer, but * only if the pointer is the same as a previously sampled value of * the pointer. * * @param cmp Value of pointer previously retrieved with get(). * @param x New value of pointer. * @return true if the pointer could be set to x (i.e. its value was * still cmp with the mutex locked.) */ template<class Y, class X> bool compare_and_set(const boost::shared_ptr<Y>& cmp, boost::shared_ptr<X>& x) { bool r = false; boost::recursive_try_mutex::scoped_try_lock l(mutex, false); if (l.try_lock()) { r = ptr == cmp; if (r) ptr = x; } return r; } private: base_ptr ptr; mutable boost::recursive_try_mutex mutex; }; // atomic_shared #endif
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk