Would anybody like a wrapper atomic_shared<T> around shared_ptr<T>?

Hi all, 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... 2) If so, how does one go about making such a donation? 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... Thanks in advance for any help/input you can give. -- George T. Talbot <gtalbot@locuspharma.com> /** \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 George T. Talbot, 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 <pthread.h> #include <cerrno> #include <stdexcept> #include <boost/shared_ptr.hpp> #include <boost/pointer_cast.hpp> //#define SPINLOCK_ABORT #ifdef SPINLOCK_ABORT_ON_ERROR #define SPINLOCK_ABORT ::abort(); #else #define SPINLOCK_ABORT #endif /** atomic_shared<T> wraps boost::shared_ptr<T> with a spinlock 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 { /** Initialize a spinlock protecting a shared_ptr<T>. */ static void init(pthread_spinlock_t& l) { int rv = ::pthread_spin_init(&l, PTHREAD_PROCESS_PRIVATE); if (rv) { SPINLOCK_ABORT errno = rv; throw std::runtime_error("can't initialize spinlock"); } } /** Used to lock and unlock a spinlock protecting the shared_ptr<>. * Saves typing. */ struct lock_t { explicit lock_t(pthread_spinlock_t& lock) : m_lock(lock) { if (::pthread_spin_lock(&m_lock)) { SPINLOCK_ABORT throw std::runtime_error("can't lock spinlock"); } } lock_t(pthread_spinlock_t& lock, int) // Assumes already locked. : m_lock(lock) { } ~lock_t() { if (::pthread_spin_unlock(&m_lock)) { SPINLOCK_ABORT } } private: pthread_spinlock_t& m_lock; }; atomic_shared& operator=(const atomic_shared&); public: typedef boost::shared_ptr<T> base_ptr; /** construct an empty pointer. */ atomic_shared() : ptr(), spinlock() { init(spinlock); } /** 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()), spinlock() { init(spinlock); } /** construct a protected pointer from a bare one. */ template<class Y> explicit atomic_shared(Y* p) : ptr(p), spinlock() { init(spinlock); } /** construct a protected pointer from a shared_ptr. */ template<class U> explicit atomic_shared(const boost::shared_ptr<U>& p) : ptr(p), spinlock() { init(spinlock); } /** destructor with rudimentary error checking. */ ~atomic_shared() { int rv = ::pthread_spin_destroy(&spinlock); // For debugging, drop us into the debugger if we can't destroy the // spinlock. assert(rv == 0); (void) rv; } /** Is it not null? */ operator bool() const { bool rv; { lock_t l(spinlock); rv = ptr; } return rv; } /** Is it null? */ bool operator!() const { bool rv; { lock_t l(spinlock); 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; { lock_t l(spinlock); 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 spinlock locked. */ template<class Y, class X> bool compare_and_set(const boost::shared_ptr<Y>& cmp, boost::shared_ptr<X>& x) { bool r; { int rv = ::pthread_spin_trylock(&spinlock); switch (rv) { case EBUSY: return false; case 0: break; default: SPINLOCK_ABORT errno = rv; throw std::logic_error("can't pthread_spin_trylock"); } lock_t l(spinlock, 0); // already locked r = ptr == cmp; if (r) ptr = x; } return r; } private: base_ptr ptr; mutable pthread_spinlock_t spinlock; }; // atomic_shared #endif

Hi George, On Wed, Jul 23, 2008 at 6:04 AM, Talbot, George <Gtalbot@locuspharma.com> 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.
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... ;-) )
From what I gather, this is something that may have to go through a mini-review. The veterans can answer this question better though.
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? Another issue I see is the dependence to ::pthread_spin_lock() -- can you use a shared recursive_mutex instead? HTH -- Dean Michael C. Berris Software Engineer, Friendster, Inc.

Talbot, George:
Hi all,
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.
The SVN now contains an implementation of the recently accepted for inclusion into C++0x http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2674.htm This feature be part of the upcoming 1.36.
participants (3)
-
Dean Michael Berris
-
Peter Dimov
-
Talbot, George