|
Boost : |
From: Talbot, George (Gtalbot_at_[hidden])
Date: 2006-11-15 09:37:57
I've been playing around with the atomic_shared example class that I
posted, and I'm not entirely happy with a spinlock-based solution to a
shared_ptr type class.
To implement operator= for a class like this has two issues:
1) operator= for an atomic shared_ptr can only be "best effort". If
another thread wins out, so be it. I'm going to, for the purposes of
this discussion, assume that this is OK, and that if you don't like it,
you'll use compare_and_set/swap to do a better job.
2) Without any sort of 2-pointer-wide atomic assignment in hardware,
operator= really looks like this:
lock the source pointer
make a local copy of it
release the source pointer
lock the destination
set the value of the destination from the copy
unlock the destination
This won't deadlock, but has to make an extra copy of the pointer
locally. To avoid the copy, we would have to lock both the source and
destination before the assignment of the underlying shared_ptr, which,
in the code I sent I did, but is deadlock-prone. See the implementation
of swap for even more inefficiency.
However, what is the "right" way to do this?
I read the stuff from Chris Thommason and I have to admit that I'm not
too sure how I'd wrap shared_ptr with it to make this work. Can I have
a bit of brainstorming assistance?
P.S. Appended is a version of atomic_shared using spinlocks that I
don't think deadlocks, but I could be wrong. Can I get a bit of help
looking at it from more experienced eyes?
P.S. Peter Dimov: Is it OK that I put your name on this as one of the
authors? You had told me in previous posts to the list how the
operations should likely work with a spinlock, so it seemed appropriate.
-- 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 * read/write operations. * * (C) Copyright George 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 <boost/shared_ptr.hpp> #include <pthread.h> #include <stdexcept> #include <cerrno> #include <cassert> #ifdef ATOMIC_ABORT_ON_ERROR #define ATOMIC_SHARED_ABORT ::abort(); #else #define ATOMIC_SHARED_ABORT #endif /** A wrapper for shared_ptr that implements atomic assignments and compare_and_swap. */ template<class T> class atomic_shared { typedef boost::shared_ptr<T> base_ptr; /** Lock a spinlock protecting an atomic pointer. * * @param l Spinlock to lock. * @param should_throw If true, thrown an exception if lock fails. */ static void lock(pthread_spinlock_t& l, bool should_throw = true) { int rv = ::pthread_spin_lock(&l); if (rv) { errno = rv; ATOMIC_SHARED_ABORT if (should_throw) { throw std::logic_error("can't lock spinlock"); } } } /** Unlock a spinlock protecting an atomic pointer. */ static void unlock(pthread_spinlock_t& l) { int rv = ::pthread_spin_unlock(&l); if (rv) { ATOMIC_SHARED_ABORT errno = rv; throw std::logic_error("can't lock spinlock"); } } /** Initialize a spinlock protecting an atomic pointer. */ static void init(pthread_spinlock_t& l) { int rv = ::pthread_spin_init(&l, PTHREAD_PROCESS_PRIVATE); if (rv) { ATOMIC_SHARED_ABORT errno = rv; throw std::logic_error("can't initialize spinlock"); } } public: typedef typename base_ptr::element_type element_type; ///< Type of element to which pointer points. typedef typename base_ptr::value_type value_type; ///< Same as element_type. typedef typename base_ptr::pointer pointer; ///< Pointer to refcounted object. typedef typename base_ptr::reference reference; ///< Reference to refcounted object. /** Initialize an empty spinlock. */ atomic_shared() { init(spinlock); } /** Create a managed pointer from a bare one. */ template<class Y> explicit atomic_shared(Y* p) : ptr(p) { init(spinlock); } /** Create a managed pointer from a bare one, and a custom deleter. */ template<class Y, class D> atomic_shared(Y * p, D d) : ptr(p, d) { init(spinlock); } atomic_shared(const base_ptr& p) : ptr(p) { init(spinlock); } atomic_shared(const atomic_shared<T>& p) { init(spinlock); lock(p.spinlock); ptr = p.ptr; unlock(p.spinlock); } atomic_shared<T>& operator=(atomic_shared<T> p) // local copy to avoid deadlock. { if (&p != this) { lock(spinlock); ptr = p.ptr unlock(spinlock); } return *this; } template<class Y> atomic_shared(const atomic_shared<Y>& p) { init(spinlock); lock(p.spinlock); ptr = p.ptr; unlock(p.spinlock); } template<class Y> atomic_shared<T>& operator=(atomic_shared<Y> p) // local copy to avoid deadlock. { lock(spinlock); ptr = p.ptr unlock(spinlock); return *this; } ~atomic_shared() { // Lock the spinlock to hold off all other copiers. lock(spinlock, false); // Destroy the spinlock--this should cause all other copiers to // either assert or throw. int rv = ::pthread_spin_destroy(&spinlock); // For debugging, drop us into the debugger if we can't destroy the // spinlock. if (rv) { ATOMIC_SHARED_ABORT } } /** Reset the pointer to an empty pointer. */ void reset() { lock(spinlock); ptr.reset(); unlock(spinlock); } /** Reset the pointer and replace it with a new bare pointer. */ template<class Y> void reset(Y* p) { lock(spinlock); ptr.reset(p); unlock(spinlock); } /** Reset the pointer and replace it with a new bare pointer with custom deleter. */ template<class Y, class D> void reset(Y* p, D d) { lock(spinlock); ptr.reset(p, d); unlock(spinlock); } reference operator*() const { return ptr.operator*(); } T* operator->() const { return ptr.operator->(); } T* get() const { return ptr.get(); } operator bool() const { return ptr; } bool operator!() const { return !ptr; } bool unique() const { return ptr.unique(); } long use_count() const { return ptr.use_count(); } /** Atomically swap the pointer with another pointer. */ void swap(atomic_shared<T>& other) { atomic_shared<T> temp(*this); atomic_shared<T> temp2(other); lock(spinlock); ptr = temp2.ptr; unlock(spinlock); lock(other.spinlock); other = temp.ptr; unlock(other.spinlock); } /** 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. * * @return true if the swap was successful. */ bool compare_and_set(const atomic_shared<T>& cmp, atomic_shared<T> xchg) // Note that xchg is a local copy. { int rv = ::pthread_spin_trylock(&spinlock); switch (rv) { case EBUSY: if ((void*)&xchg < (void*)this) unlock(xchg.spinlock); return false; case 0: break; default: ATOMIC_SHARED_ABORT errno = rv; throw std::logic_error("can't pthread_spin_trylock"); } bool r = ptr == cmp.ptr; if (r) ptr.swap(xchg.ptr); unlock(spinlock); return r; } private: template<class Y> friend class atomic_shared; 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