Boost logo

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