looking for elegant C++ abstraction around pthread_key_t...

Discussion in 'C++' started by Chris M. Thomasson, Oct 30, 2008.

  1. Here is what I am playing around with now:
    _________________________________________________________________
    /* Simple TSD Object
    ______________________________________________________________*/
    #include <pthread.h>
    #include <cstdio>


    #if defined(_MSC_VER)
    # define DECLSPEC_CDECL __cdecl
    #elif defined(__GNUC__)
    # define DECLSPEC_CDECL __attribute__((cdecl))
    #else
    # error MSVC or GCC REQUIRED!!!!! ;^(...
    #endif


    template<typename T>
    class tsd {
    pthread_key_t m_key;

    static void DECLSPEC_CDECL tsd_dtor(void* state) {
    delete reinterpret_cast<T*>(state);
    }

    public:
    struct main_guard {
    tsd& m_tsd;

    main_guard(tsd& tsd_) : m_tsd(tsd_) {

    }

    ~main_guard() {
    m_tsd.clear();
    }
    };

    tsd() {
    pthread_key_create(&m_key, tsd_dtor);
    }

    ~tsd() {
    pthread_key_delete(m_key);
    std::printf("(%p)->tsd<T>::~tsd()\nhit <ENTER> to continue...",
    (void*)this);
    std::fflush(stdout);
    std::getchar();
    }

    T& instance() const {
    T* obj = reinterpret_cast<T*>(pthread_getspecific(m_key));
    if (! obj) {
    obj = new T();
    pthread_setspecific(m_key, obj);
    }
    return *obj;
    }

    void clear() {
    delete reinterpret_cast<T*>(pthread_getspecific(m_key));
    pthread_setspecific(m_key, NULL);
    }
    };




    /* Simple Usage Example
    ______________________________________________________________*/
    #include <cassert>


    static tsd<struct foo> g_foo_tsd;
    static tsd<struct foo2> g_foo2_tsd;
    static tsd<struct foo3> g_foo3_tsd;


    struct foo {
    foo() {
    std::printf("(%p)->foo::foo()\n", (void*)this);
    }

    ~foo() {
    std::printf("(%p)->foo::~foo()\n", (void*)this);
    }
    };


    struct foo2 {
    foo2() {
    std::printf("(%p)->foo2::foo2()\n", (void*)this);
    }

    ~foo2() {
    std::printf("(%p)->foo2::~foo2()\n", (void*)this);
    }
    };


    struct foo3 {
    foo3() {
    std::printf("(%p)->foo3::foo3()\n", (void*)this);
    }

    ~foo3() {
    std::printf("(%p)->foo3::~foo3()\n", (void*)this);
    }
    };


    extern "C" void* thread_entry(void* state) {
    {
    foo& f1 = g_foo_tsd.instance();
    foo& f2 = g_foo_tsd.instance();
    foo& f3 = g_foo_tsd.instance();
    assert(&f1 == &f2 && &f2 == &f3);
    }

    {
    foo2& f1 = g_foo2_tsd.instance();
    foo2& f2 = g_foo2_tsd.instance();
    foo2& f3 = g_foo2_tsd.instance();
    assert(&f1 == &f2 && &f2 == &f3);
    }

    {
    foo3& f1 = g_foo3_tsd.instance();
    foo3& f2 = g_foo3_tsd.instance();
    foo3& f3 = g_foo3_tsd.instance();
    assert(&f1 == &f2 && &f2 == &f3);
    }

    return 0;
    }


    int main(void) {
    {
    tsd<foo>::main_guard tsd_main_guard(g_foo_tsd);
    tsd<foo2>::main_guard tsd_main_guard2(g_foo2_tsd);
    tsd<foo3>::main_guard tsd_main_guard3(g_foo3_tsd);

    pthread_t tid[2];
    pthread_create(&tid[0], NULL, thread_entry, NULL);
    pthread_create(&tid[1], NULL, thread_entry, NULL);

    {
    foo& f1 = g_foo_tsd.instance();
    foo& f2 = g_foo_tsd.instance();
    foo& f3 = g_foo_tsd.instance();
    assert(&f1 == &f2 && &f2 == &f3);
    }

    {
    foo2& f1 = g_foo2_tsd.instance();
    foo2& f2 = g_foo2_tsd.instance();
    foo2& f3 = g_foo2_tsd.instance();
    assert(&f1 == &f2 && &f2 == &f3);
    }

    {
    foo3& f1 = g_foo3_tsd.instance();
    foo3& f2 = g_foo3_tsd.instance();
    foo3& f3 = g_foo3_tsd.instance();
    assert(&f1 == &f2 && &f2 == &f3);
    }

    pthread_join(tid[1], NULL);
    pthread_join(tid[0], NULL);
    }
    std::puts("\n\n\n__________________\nhit <ENTER> to exit...");
    std::fflush(stdout);
    std::getchar();
    return 0;
    }
    _________________________________________________________________




    As you can see it uses compiler specific extensions in order to ensure the
    procedure `tsd<T>::tsd_dtor()' has C linkage. I am doing this in order to
    get around having to dynamically create a base-class, helper object and a
    free extern "C" function. I can't really see any way around having to go
    through that mess without resorting to compiler extensions. Humm, if I were
    to use a free function, I think I would have to do something ugly like:
    _________________________________________________________________
    /* Simple TSD Object
    ______________________________________________________________*/
    #include <pthread.h>
    #include <cstdio>


    struct tsd_object_base {
    virtual ~tsd_object_base() = 0;
    };

    tsd_object_base::~tsd_object_base() {}


    extern "C" void tsd_object_dtor(void* state) {
    delete reinterpret_cast<tsd_object_base*>(state);
    }


    template<typename T>
    class tsd {
    struct tsd_object : public tsd_object_base {
    T m_object;
    tsd_object() : m_object() {}
    };


    pthread_key_t m_key;


    public:
    struct main_guard {
    tsd& m_tsd;

    main_guard(tsd& tsd_) : m_tsd(tsd_) {

    }

    ~main_guard() {
    m_tsd.clear();
    }
    };

    tsd() {
    pthread_key_create(&m_key, tsd_object_dtor);
    }

    ~tsd() {
    pthread_key_delete(m_key);
    std::printf("(%p)->tsd<T>::~tsd()\nhit <ENTER> to continue...",
    (void*)this);
    std::fflush(stdout);
    std::getchar();
    }

    T& instance() const {
    tsd_object* obj =
    reinterpret_cast<tsd_object*>(pthread_getspecific(m_key));
    if (! obj) {
    obj = new tsd_object();
    pthread_setspecific(m_key, obj);
    }
    return obj->m_object;
    }

    void clear() {
    delete reinterpret_cast<tsd_object*>(pthread_getspecific(m_key));
    pthread_setspecific(m_key, NULL);
    }
    };


    // [...]
    _________________________________________________________________




    This works fine, but IMVHO, its kind of messy. However, it is standard wrt
    POSIX rules, and a heck of a lot more portable. Is there any way to keep
    maximum portability, yet remove the need for helper classes? I suppose I
    could do two versions and #ifdef them if the compiler does not support the
    extensions I am looking for... Humm... Need advise!


    Thanks.
     
    Chris M. Thomasson, Oct 30, 2008
    #1
    1. Advertising

  2. "Chris M. Thomasson" <> wrote in message
    news:rBrOk.33$...
    > Here is what I am playing around with now:
    > _________________________________________________________________

    [...]
    > _________________________________________________________________
    >
    > As you can see it uses compiler specific extensions in order to ensure the
    > procedure `tsd<T>::tsd_dtor()' has C linkage. I am doing this in order to
    > get around having to dynamically create a base-class, helper object and a
    > free extern "C" function. I can't really see any way around having to go
    > through that mess without resorting to compiler extensions. Humm, if I
    > were to use a free function, I think I would have to do something ugly
    > like:
    > _________________________________________________________________

    [...]
    > _________________________________________________________________
    >
    > This works fine, but IMVHO, its kind of messy. However, it is standard wrt
    > POSIX rules, and a heck of a lot more portable. Is there any way to keep
    > maximum portability, yet remove the need for helper classes? I suppose I
    > could do two versions and #ifdef them if the compiler does not support the
    > extensions I am looking for... Humm... Need advise!

    [...]

    Well, I suppose I could allow the user to pass in a free function that must
    have C linkage for use as the actual TSD dtor; something like:
    _________________________________________________________________
    /* Simple TSD Object
    ______________________________________________________________*/
    #include <pthread.h>
    #include <cstdio>


    template<typename T>
    class tsd {
    pthread_key_t m_key;
    typedef void (func_dtor_t) (void*);
    func_dtor_t* const m_fp_dtor;


    public:
    struct main_guard {
    tsd& m_tsd;

    main_guard(tsd& tsd_) : m_tsd(tsd_) {

    }

    ~main_guard() {
    m_tsd.clear();
    }
    };

    tsd(func_dtor_t* const fp_dtor) : m_fp_dtor(fp_dtor) {
    pthread_key_create(&m_key, fp_dtor);
    }

    ~tsd() {
    pthread_key_delete(m_key);
    std::printf("(%p)->tsd<T>::~tsd()\nhit <ENTER> to continue...",
    (void*)this);
    std::fflush(stdout);
    std::getchar();
    }

    T& instance() const {
    T* obj = reinterpret_cast<T*>(pthread_getspecific(m_key));
    if (! obj) {
    obj = new T();
    pthread_setspecific(m_key, obj);
    }
    return *obj;
    }

    void clear() {
    T* obj = reinterpret_cast<T*>(pthread_getspecific(m_key));
    pthread_setspecific(m_key, NULL);
    if (obj) {
    m_fp_dtor(obj);
    }
    }
    };




    /* Simple Usage Example
    ______________________________________________________________*/
    #include <cassert>


    extern "C" void foo_dtor(void* state);
    extern "C" void foo2_dtor(void* state);
    extern "C" void foo3_dtor(void* state);


    static tsd<struct foo> g_foo_tsd(foo_dtor);
    static tsd<struct foo2> g_foo2_tsd(foo2_dtor);
    static tsd<struct foo3> g_foo3_tsd(foo3_dtor);


    struct foo {
    foo() {
    std::printf("(%p)->foo::foo()\n", (void*)this);
    }

    ~foo() {
    std::printf("(%p)->foo::~foo()\n", (void*)this);
    }
    };

    void foo_dtor(void* state) {
    delete reinterpret_cast<foo*>(state);
    }


    struct foo2 {
    foo2() {
    std::printf("(%p)->foo2::foo2()\n", (void*)this);
    }

    ~foo2() {
    std::printf("(%p)->foo2::~foo2()\n", (void*)this);
    }
    };

    void foo2_dtor(void* state) {
    delete reinterpret_cast<foo2*>(state);
    }


    struct foo3 {
    foo3() {
    std::printf("(%p)->foo3::foo3()\n", (void*)this);
    }

    ~foo3() {
    std::printf("(%p)->foo3::~foo3()\n", (void*)this);
    }
    };

    void foo3_dtor(void* state) {
    delete reinterpret_cast<foo3*>(state);
    }





    extern "C" void* thread_entry(void* state) {
    {
    foo& f1 = g_foo_tsd.instance();
    foo& f2 = g_foo_tsd.instance();
    foo& f3 = g_foo_tsd.instance();
    assert(&f1 == &f2 && &f2 == &f3);
    }

    {
    foo2& f1 = g_foo2_tsd.instance();
    foo2& f2 = g_foo2_tsd.instance();
    foo2& f3 = g_foo2_tsd.instance();
    assert(&f1 == &f2 && &f2 == &f3);
    }

    {
    foo3& f1 = g_foo3_tsd.instance();
    foo3& f2 = g_foo3_tsd.instance();
    foo3& f3 = g_foo3_tsd.instance();
    assert(&f1 == &f2 && &f2 == &f3);
    }

    return 0;
    }


    int main(void) {
    {
    tsd<foo>::main_guard tsd_main_guard(g_foo_tsd);
    tsd<foo2>::main_guard tsd_main_guard2(g_foo2_tsd);
    tsd<foo3>::main_guard tsd_main_guard3(g_foo3_tsd);

    pthread_t tid[2];
    pthread_create(&tid[0], NULL, thread_entry, NULL);
    pthread_create(&tid[1], NULL, thread_entry, NULL);

    {
    foo& f1 = g_foo_tsd.instance();
    foo& f2 = g_foo_tsd.instance();
    foo& f3 = g_foo_tsd.instance();
    assert(&f1 == &f2 && &f2 == &f3);
    }

    {
    foo2& f1 = g_foo2_tsd.instance();
    foo2& f2 = g_foo2_tsd.instance();
    foo2& f3 = g_foo2_tsd.instance();
    assert(&f1 == &f2 && &f2 == &f3);
    }

    {
    foo3& f1 = g_foo3_tsd.instance();
    foo3& f2 = g_foo3_tsd.instance();
    foo3& f3 = g_foo3_tsd.instance();
    assert(&f1 == &f2 && &f2 == &f3);
    }

    pthread_join(tid[1], NULL);
    pthread_join(tid[0], NULL);
    }
    std::puts("\n\n\n__________________\nhit <ENTER> to exit...");
    std::fflush(stdout);
    std::getchar();
    return 0;
    }
    _________________________________________________________________




    That would solve my problem, but might not be all that user friendly. For
    one, the poor users would
    __always_need__ to ensure that the free function they pass to the `tsd<T>'
    ctor has proper C linkage... Not to sure about this... Humm...


    Any advise? I am NOT a C++ guy! I need help here...

    :^(...
     
    Chris M. Thomasson, Oct 31, 2008
    #2
    1. Advertising

  3. Maxim Yegorushkin, Oct 31, 2008
    #3
  4. "Maxim Yegorushkin" <> wrote in message
    news:...
    On Oct 30, 11:48 pm, "Chris M. Thomasson" <> wrote:
    > > Here is what I am playing around with now:


    > []


    > Have you looked at
    > http://www.boost.org/doc/libs/1_36_0/doc/html/thread/thread_local_storage.html ?


    Yeah. I need to look at the actual source-code and see how they deal with
    handling the TSD dtor callback. It needs to have C-linkage, and I want to
    see how they get it to understand what type its dealing with. Do they use a
    base-class, or other trickery that I am not familiar with.
     
    Chris M. Thomasson, Oct 31, 2008
    #4
  5. "Chris M. Thomasson" <> wrote in message
    news:eek:%HOk.1694$...
    >
    > "Maxim Yegorushkin" <> wrote in message
    > news:...
    > On Oct 30, 11:48 pm, "Chris M. Thomasson" <> wrote:
    >> > Here is what I am playing around with now:

    >
    >> []

    >
    >> Have you looked at
    >> http://www.boost.org/doc/libs/1_36_0/doc/html/thread/thread_local_storage.html ?

    >
    > Yeah. I need to look at the actual source-code and see how they deal with
    > handling the TSD dtor callback. It needs to have C-linkage, and I want to
    > see how they get it to understand what type its dealing with. Do they use
    > a base-class, or other trickery that I am not familiar with.



    Well, it seems like they are using an abstract base-class. I may be wrong;
    the Boost source-code is pretty hardcore.
     
    Chris M. Thomasson, Oct 31, 2008
    #5
  6. On Oct 31, 6:51 pm, "Chris M. Thomasson" <> wrote:
    > "Chris M. Thomasson" <> wrote in messagenews:eek:%HOk.1694$...
    >
    >
    >
    > > "Maxim Yegorushkin" <> wrote in message
    > >news:....
    > > On Oct 30, 11:48 pm, "Chris M. Thomasson" <> wrote:
    > >> > Here is what I am playing around with now:

    >
    > >> []

    >
    > >> Have you looked at
    > >>http://www.boost.org/doc/libs/1_36_0/doc/html/thread/thread_local_sto....?

    >
    > > Yeah. I need to look at the actual source-code and see how they deal with
    > > handling the TSD dtor callback.


    The source code which deals with pthread specifics of TLS is here
    http://boost.cvs.sourceforge.net/vi.../thread/src/tss.cpp?revision=HEAD&view=markup

    However, boost does not map thread_specific_ptr to pthread_key_t 1:1.
    Instead, they boost threads library uses only one pthread_key_t and
    implements its own clean up handler mechanism.

    > > It needs to have C-linkage, and I want to
    > > see how they get it to understand what type its dealing with. Do they use
    > > a base-class, or other trickery that I am not familiar with.


    Although all POSIX APIs require pointers to functions with C linkage,
    on practice all C++ functions and static member functions have C
    linkage, it is name mangling what is different. I don't know of any
    platform/compiler where this is not true (my knowledge is limited
    though).

    This is what I did with my own thread specific pointer (intended to be
    used for global variables mostly):

    http://ccull.svn.sourceforge.net/vi...ude/threads/tls_ptr..h?revision=1&view=markup

    --
    Max
     
    Maxim Yegorushkin, Oct 31, 2008
    #6
  7. "Maxim Yegorushkin" <> wrote in message
    news:...
    On Oct 31, 6:51 pm, "Chris M. Thomasson" <> wrote:
    > > "Chris M. Thomasson" <> wrote in
    > > messagenews:eek:%HOk.1694$...
    > >
    > >
    > >
    > > > "Maxim Yegorushkin" <> wrote in message
    > > >news:...
    > > > On Oct 30, 11:48 pm, "Chris M. Thomasson" <> wrote:
    > > >> > Here is what I am playing around with now:

    > >
    > > >> []

    > >
    > > >> Have you looked at
    > > >>http://www.boost.org/doc/libs/1_36_0/doc/html/thread/thread_local_sto...?

    > >
    > > > Yeah. I need to look at the actual source-code and see how they deal
    > > > with
    > > > handling the TSD dtor callback.


    > The source code which deals with pthread specifics of TLS is here
    > http://boost.cvs.sourceforge.net/vi.../thread/src/tss.cpp?revision=HEAD&view=markup


    > However, boost does not map thread_specific_ptr to pthread_key_t 1:1.
    > Instead, they boost threads library uses only one pthread_key_t and
    > implements its own clean up handler mechanism.


    Ahhh... Okay. Humm, now I wonder why they did it that way. This kind of
    seems odd to me. I know that you have to do that on Windows, but under a
    POSIX system; why? I suppose it would be good in the sense of you won't run
    out of TSD keys. Interesting.




    > > > It needs to have C-linkage, and I want to
    > > > see how they get it to understand what type its dealing with. Do they
    > > > use
    > > > a base-class, or other trickery that I am not familiar with.


    > Although all POSIX APIs require pointers to functions with C linkage,
    > on practice all C++ functions and static member functions have C
    > linkage, it is name mangling what is different. I don't know of any
    > platform/compiler where this is not true (my knowledge is limited
    > though).


    > This is what I did with my own thread specific pointer (intended to be
    > used for global variables mostly):


    > http://ccull.svn.sourceforge.net/viewvc/ccull/trunk/include/threads/tls_ptr.h?revision=1&view=markup


    Humm... Well, I was going for something that would be as portable as
    possible. Murphy's Law comes to mind:


    The first use of your TSD will of course be on a platform that does not use
    C-linkage for static member functions.


    Yikes! ;^)
     
    Chris M. Thomasson, Oct 31, 2008
    #7
  8. "Chris M. Thomasson" <> writes:

    > "Chris M. Thomasson" <> wrote in message
    > news:eek:%HOk.1694$...
    >>
    >> "Maxim Yegorushkin" <> wrote in message
    >> news:...
    >> On Oct 30, 11:48 pm, "Chris M. Thomasson" <> wrote:
    >>> > Here is what I am playing around with now:

    >>
    >>> []

    >>
    >>> Have you looked at
    >>> http://www.boost.org/doc/libs/1_36_0/doc/html/thread/thread_local_storage.html
    >>> ?

    >>
    >> Yeah. I need to look at the actual source-code and see how they deal
    >> with handling the TSD dtor callback. It needs to have C-linkage, and
    >> I want to see how they get it to understand what type its dealing
    >> with. Do they use a base-class, or other trickery that I am not
    >> familiar with.

    >
    >
    > Well, it seems like they are using an abstract base-class.


    Yes, I use an abstract base class.

    In boost, the actual TSD value is a pointer to a data structure which
    contains *all* the thread-specific data for a given thread (it's
    currently a linked list). Each entry has its own cleanup function,
    which is called through a pointer to an abstract base class.

    The TSD dtor callback is an extern "C" function that takes a pointer
    to the data structure and calls the cleanup function for each
    thread_specific_ptr value used by the current thread.

    > I may be wrong; the Boost source-code is pretty hardcore.


    :)

    Anthony
    --
    Anthony Williams
    Author of C++ Concurrency in Action | http://www.manning.com/williams

    Custom Software Development | http://www.justsoftwaresolutions.co.uk
    Just Software Solutions Ltd, Registered in England, Company Number 5478976.
    Registered Office: 15 Carrallack Mews, St Just, Cornwall, TR19 7UL, UK
     
    Anthony Williams, Oct 31, 2008
    #8
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Alex
    Replies:
    2
    Views:
    557
  2. mikegw
    Replies:
    5
    Views:
    389
    Nick Keighley
    May 24, 2004
  3. Piet
    Replies:
    0
    Views:
    564
  4. Chris M. Thomasson
    Replies:
    19
    Views:
    784
    Chris M. Thomasson
    Nov 9, 2008
  5. Network/Software Buyer
    Replies:
    0
    Views:
    430
    Network/Software Buyer
    May 23, 2010
Loading...

Share This Page