Boost logo

Boost :

From: Martin Wille (mw8329_at_[hidden])
Date: 2003-11-18 07:15:32


I wrote:

>> What we, or you if you want to contribute, can do: find a way of
>> storing many thread specific items without wasting that many thread
>> keys. (I'll post separately on this)
>
>
> Spirit already uses a technique for reducing the number of
> tss keys used (the problem to solve was to reduce
> one key/grammar object to one key/grammar class). Maybe,
> some of the code can be transformed into a general approach
> of reducing the number of thread keys used by
> boost::thread_specific_ptr instances.

There was not too much response to my question wether an
implementation of that idea would be considered a useful
addition to Boost.Thread. I guess this was at least partly
due to the lack of code.

Attached is some code that implements that idea.
The code uses some part of Spirit's implementation
details. Of course, that would have to be changed if it
becomes a part of Boost.Thread. Nevertheless, I believe
the code is working and would solve the problem of the OP
(when used for Spirit's grammar and closure implementations).
However, the alternative_thread_specific_ptr<T> is useful
outside Spirit, too.
The code does not use any native thread APIs and is
implemented on top of the Boost.Thread API.

The interface offered by alternative_thread_specific_ptr<T> is
the same as the interface of thread_specific_ptr<T> (this is
why I didn't write documentation for now), with these exceptions:

- .get() may throw std::bad_alloc().
- operator ->() and operator *() may throw std::bad_alloc().
- .reset(p!=0) may throw std::bad_alloc(), but
   .release() and .reset(0) don't throw.

Destructing an alternative_thread_specific_pointer<T> instance
while one or more threads have stored data (!=0) through
that instance will invoke undefined behaviour (thanks for
pointing me to a discussion regarding that, Alexander).

I'd welcome naming suggestions, "alternative_thread_specific_ptr"
is a bit unwieldy.

Is there interest to make alternative_thread_specific_ptr<>
a part of Boost.Thread? If so, what would be the procedure
(it is a small addition to an existing library, so I don't
know wether a formal review is required)?

Regards,
m


//----------------------------------------------------------------------------//
// Copyright (C) 2003 Martin Wille
//
// Use, modification, and distribution is subject to 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 BOOST_THREAD_ATSS_HPP
#define BOOST_THREAD_ATSS_HPP

#include <boost/thread/tss.hpp>
#include <boost/spirit/core/non_terminal/impl/object_with_id.ipp>

namespace boost {

namespace detail {

struct thread_atsp_tag {};

class BOOST_THREAD_DECL atss
    : private noncopyable
    , private boost::spirit::impl::object_with_id<thread_atsp_tag>
{
public:
    typedef ::boost::spirit::impl::
                object_with_id<thread_atsp_tag>::object_id key_type;

    atss(void (*cleanup)(void*));
    ~atss();

    void* get() const;
    void* get_nothrow() const;
    void set(void* value) const;

    inline key_type key() const { return get_object_id(); }
    inline atss const *get_next() const { return next; }
    inline void call_cleanup(void *p) const { cleanup(p); }

private:
    void (*cleanup)(void *);
    atss *next;
    atss *previous;
};

} // namespace ::boost::detail

template <typename T>
class alternative_thread_specific_ptr : private noncopyable
{
private:
    typedef alternative_thread_specific_ptr<T> self_t;

public:
    alternative_thread_specific_ptr()
        : m_tss(&self_t::cleanup) { }

    T* get() const { return static_cast<T*>(m_tss.get()); } // throw (bad_alloc)
    T* operator->() const { return get(); }
    T& operator*() const { return *get(); }
    T* release() // does not throw
    {
        T* temp = get_nothrow();
        if (temp) m_tss.set(0);
        return temp;
    }
    void reset(T* p=0) // does not throw if p==0, throw (bad_alloc), otherwise
    {
        T* cur = get_nothrow();
        if (cur == p) return;
        delete cur;
        m_tss.set(p);
    }

private:
    T *get_nothrow() { return static_cast<T*>(m_tss.get_nothrow()); }

    static void cleanup(void* p) { delete static_cast<T*>(p); }
    detail::atss m_tss;
};

} // namespace ::boost

#endif // BOOST_THREAD_ATSS_HPP


//----------------------------------------------------------------------------//
// Copyright (C) 2003 Martin Wille
//
// Use, modification, and distribution is subject to 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)
//
//----------------------------------------------------------------------------//

#include <boost/thread/atss.hpp>
#include <boost/thread/mutex.hpp>
#include <vector>
#include <new> // std::auto_ptr

namespace
{
    typedef boost::detail::atss::key_type key_type;

    // a threadsafely used list of all atss objects
    boost::mutex access_to_all_atss;
    boost::detail::atss *all_atss = 0;

    // the thread specific data accessed though atss objects
    // basically a vector<void*> that represents the key_type -> void *
    // mapping
    class thread_specific_data
    {
    public:
        thread_specific_data(key_type initial_size) : data(initial_size) {}

        inline void *& operator[](key_type key) // forwards to the vector
        { return data[key]; }

        inline key_type size() const // forwards to the vector
        { return data.size(); }

        inline void resize(key_type new_size) // forwards to the vector
        { data.resize(new_size); }

        ~thread_specific_data()
        { // call the deleters for all the items stored in the vector
            boost::mutex::scoped_lock lock(access_to_all_atss);
            for (boost::detail::atss const *i = all_atss; i; i = i->get_next())
            {
                key_type const key = i->key();
                if (key < size() && data[key] != 0)
                    i->call_cleanup(data[key]);
            }
        }

    private:
        std::vector<void *> data;

    };

    inline boost::thread_specific_ptr<thread_specific_data> &
    get_tsp()
    {
        static boost::thread_specific_ptr<thread_specific_data> static_tsp;
        return static_tsp;
    }

    // the accessor function for the thread specific data
    // throws: std::bad_alloc. returns a reference to the void * element.
    void *&
    access_tsd(boost::detail::atss const &atss)
    {
        boost::thread_specific_ptr<thread_specific_data> &tsp = get_tsp();
        key_type const key(atss.key());

        if (!tsp.get())
        { // create a new vector large enough for the current key
            // done at most once per thread
            std::auto_ptr<thread_specific_data> tmp
            (
                new thread_specific_data(key+1)
            );

            tsp.reset(tmp.get());
            tmp.release();
        }

        thread_specific_data &v = *tsp;

        if (v.size() <= key)
        { // grow the vector exponentially; that helps to make
            // this function amortized O(1)
            std::vector<void*>::size_type size(v.size()*3/2+1);
            if(size <= key)
                size = key+1;
            v.resize(size);
        }

        return v[key];
    }
}

boost::detail::atss::atss(void (*cleanup_callback)(void*))
    : cleanup(cleanup_callback)
    , next(0)
    , previous(0)
{
    assert(cleanup_callback);
    boost::mutex::scoped_lock lock(access_to_all_atss);
    get_tsp(); // this call ensures the threadsafe construction of
               // the static tsp variable used for holding the
               // threadspecific vector<> of user data.
    next = all_atss;
    all_atss = this;
    if (next!=0)
        next -> previous = this;
}

boost::detail::atss::~atss()
{
    boost::mutex::scoped_lock lock(access_to_all_atss);
    if (next!=0)
        next->previous = previous;
    if (previous!=0)
        previous->next = next;
    if (all_atss == this)
        all_atss = next;
}

void *
boost::detail::atss::get() const
{
    return access_tsd(*this);
}

void *
boost::detail::atss::get_nothrow() const
{
    boost::thread_specific_ptr<thread_specific_data> &tsp = get_tsp();
    thread_specific_data *v = tsp.get();
    if (!v) return 0;
    if (v->size() <= key()) return 0;
    return (*v)[key()];
}

void
boost::detail::atss::set(void *new_value) const
{
    access_tsd(*this) = new_value;
}


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk