$include_dir="/home/hyper-archives/boost/include"; include("$include_dir/msg-header.inc") ?>
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