|
Boost : |
From: Talbot, George (Gtalbot_at_[hidden])
Date: 2008-07-22 18:04:40
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_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 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
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk