[ovs-dev] [threads 02/11] ovs-thread: Add per-thread data support.

Ethan Jackson ethan at nicira.com
Tue Jun 25 19:56:09 UTC 2013


I don't really like the names of the functions "NAME_get" and
"NAME_get__"  could we come up with something that's a bit more
specific about the difference between them?  Maybe get_init and get?
I'm not sure what would be best . . .

Acked-by: Ethan Jackson <ethan at nicira.com>

On Mon, Jun 24, 2013 at 10:51 AM, Ben Pfaff <blp at nicira.com> wrote:
> On Fri, Jun 21, 2013 at 09:40:32AM -0400, Ed Maste wrote:
>> On 20 June 2013 20:10, Ben Pfaff <blp at nicira.com> wrote:
>> > Can you confirm that the autoconf test needs #include <threads.h> but
>> > the actual program doesn't?  It looks funny.
>>
>> Yes.  It looks like it should be added in the actual program; right
>> wow we end up with the definition leaking from another header.
>
> OK.
>
> _Thread_local is supposed to be a keyword, so it seems weird to
> #include a header to use it.  But <threads.h> is supposed to define a
> macro 'thread_local' that expands to _Thread_local.  It makes a little
> more sense to me to #include <threads.h> and then use the thread_local
> macro, so I modified the patch to do that.
>
> --8<--------------------------cut here-------------------------->8--
>
> From: Ben Pfaff <blp at nicira.com>
> Date: Mon, 24 Jun 2013 10:51:24 -0700
> Subject: [PATCH] ovs-thread: Add per-thread data support.
>
> POSIX defines a portable pthread_key_t API for per-thread data.  GCC and
> C11 have two different forms of per-thread data that are generally faster
> than the POSIX API, where they are available.  This commit adds a
> macro-based wrapper, DEFINE_PER_THREAD_DATA, that takes advantage of these
> features where they are available and falls back to the POSIX API
> otherwise.
>
> The Clang compiler implements C11 thread_local in its <threads.h>.
>
> This commit also adds a convenience wrapper for the POSIX API, via the
> DEFINE_PER_THREAD_MALLOCED_DATA macro.
>
> Signed-off-by: Ben Pfaff <blp at nicira.com>
> ---
>  configure.ac      |    1 +
>  lib/ovs-thread.h  |  204 +++++++++++++++++++++++++++++++++++++++++++++++++++++
>  m4/openvswitch.m4 |   37 +++++++++-
>  3 files changed, 241 insertions(+), 1 deletions(-)
>
> diff --git a/configure.ac b/configure.ac
> index a691963..52e8ce6 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -80,6 +80,7 @@ OVS_CHECK_XENSERVER_VERSION
>  OVS_CHECK_GROFF
>  OVS_CHECK_GNU_MAKE
>  OVS_CHECK_CACHE_TIME
> +OVS_CHECK_TLS
>
>  OVS_ENABLE_OPTION([-Wall])
>  OVS_ENABLE_OPTION([-Wno-sign-compare])
> diff --git a/lib/ovs-thread.h b/lib/ovs-thread.h
> index cafeedf..e32d74e 100644
> --- a/lib/ovs-thread.h
> +++ b/lib/ovs-thread.h
> @@ -85,5 +85,209 @@ void xpthread_cond_wait(pthread_cond_t *, pthread_mutex_t *mutex)
>  void xpthread_key_create(pthread_key_t *, void (*destructor)(void *));
>
>  void xpthread_create(pthread_t *, pthread_attr_t *, void *(*)(void *), void *);
> +
> +/* Per-thread data.
> + *
> + * Multiple forms of per-thread data exist, each with its own pluses and
> + * minuses:
> + *
> + *     - POSIX per-thread data via pthread_key_t is portable to any pthreads
> + *       implementation, and allows a destructor function to be defined.  It
> + *       only (directly) supports per-thread pointers, which are always
> + *       initialized to NULL.  It requires once-only allocation of a
> + *       pthread_key_t value.  It is relatively slow.
> + *
> + *     - The thread_local feature newly defined in C11 <threads.h> works with
> + *       any data type and initializer, and it is fast.  thread_local does not
> + *       require once-only initialization like pthread_key_t.  C11 does not
> + *       define what happens if one attempts to access a thread_local object
> + *       from a thread other than the one to which that object belongs.  There
> + *       is no provision to call a user-specified destructor when a thread
> + *       ends.
> + *
> + *     - The __thread keyword is a GCC extension similar to thread_local but
> + *       with a longer history.  __thread is not portable to every GCC version
> + *       or environment.  __thread does not restrict the use of a thread-local
> + *       object outside its own thread.
> + *
> + * Here's a handy summary:
> + *
> + *                     pthread_key_t     thread_local       __thread
> + *                     -------------     ------------     -------------
> + * portability             high               low             medium
> + * speed                    low              high               high
> + * supports destructors?    yes                no                 no
> + * needs key allocation?    yes                no                 no
> + * arbitrary initializer?    no               yes                yes
> + * cross-thread access?     yes                no                yes
> + */
> +
> +/* DEFINE_PER_THREAD_DATA(TYPE, NAME, INITIALIZER).
> + *
> + * One should prefer to use POSIX per-thread data, via pthread_key_t, when its
> + * performance is acceptable, because of its portability (see the table above).
> + * This macro is an alternatives that takes advantage of thread_local (and
> + * __thread), for its performance, when it is available, and falls back to
> + * POSIX per-thread data otherwise.
> + *
> + * Defines per-thread variable NAME with the given TYPE, initialized to
> + * INITIALIZER (which must be valid as an initializer for a variable with
> + * static lifetime).
> + *
> + * The public interface to the variable is:
> + *
> + *    TYPE *NAME_get(void)
> + *    TYPE *NAME_get__(void)
> + *
> + *       Returns the address of this thread's instance of NAME.
> + *
> + *       Use NAME_get() in a context where this might be the first use of the
> + *       per-thread variable in the program.  Use NAME_get__(), which avoids a
> + *       conditional test and is thus slightly faster, in a context where one
> + *       knows that NAME_get() has already been called previously.
> + *
> + * There are no "NAME_set()" or "NAME_set__()" functions.  To set the value of
> + * the per-thread variable, dereference the pointer returned by TYPE_get() or
> + * TYPE_get__(), e.g. *TYPE_get() = 0.
> + */
> +#if HAVE_THREAD_LOCAL || HAVE___THREAD
> +
> +#if HAVE_THREAD_LOCAL
> +#include <threads.h>
> +#elif HAVE___THREAD
> +#define thread_local __thread
> +#else
> +#error
> +#endif
> +
> +#define DEFINE_PER_THREAD_DATA(TYPE, NAME, ...)                 \
> +    typedef TYPE NAME##_type;                                   \
> +    static thread_local NAME##_type NAME##_var = __VA_ARGS__;   \
> +                                                                \
> +    static NAME##_type *                                        \
> +    NAME##_get__(void)                                          \
> +    {                                                           \
> +        return &NAME##_var;                                     \
> +    }                                                           \
> +                                                                \
> +    static NAME##_type *                                        \
> +    NAME##_get(void)                                            \
> +    {                                                           \
> +        return NAME##_get__();                                  \
> +    }
> +#else  /* no C implementation support for thread-local storage  */
> +#define DEFINE_PER_THREAD_DATA(TYPE, NAME, ...)                         \
> +    typedef TYPE NAME##_type;                                           \
> +    static pthread_key_t NAME##_key;                                    \
> +                                                                        \
> +    static NAME##_type *                                                \
> +    NAME##_get__(void)                                                  \
> +    {                                                                   \
> +        return pthread_getspecific(NAME##_key);                         \
> +    }                                                                   \
> +                                                                        \
> +    static void                                                         \
> +    NAME##_once_init(void)                                              \
> +    {                                                                   \
> +        if (pthread_key_create(&NAME##_key, free)) {                    \
> +            abort();                                                    \
> +        }                                                               \
> +    }                                                                   \
> +                                                                        \
> +    static NAME##_type *                                                \
> +    NAME##_get(void)                                                    \
> +    {                                                                   \
> +        static pthread_once_t once = PTHREAD_ONCE_INIT;                 \
> +        NAME##_type *value;                                             \
> +                                                                        \
> +        pthread_once(&once, NAME##_once_init);                          \
> +        value = NAME##_get__();                                         \
> +        if (!value) {                                                   \
> +            static const NAME##_type initial_value = __VA_ARGS__;       \
> +                                                                        \
> +            value = xmalloc(sizeof *value);                             \
> +            *value = initial_value;                                     \
> +            pthread_setspecific(NAME##_key, value);                     \
> +        }                                                               \
> +        return value;                                                   \
> +    }
> +#endif
> +
> +/* DEFINE_PER_THREAD_MALLOCED_DATA(TYPE, NAME).
> + *
> + * This is a simple wrapper around POSIX per-thread data primitives.  It
> + * defines per-thread variable NAME with the given TYPE, which must be a
> + * pointer type.  In each thread, the per-thread variable is initialized to
> + * NULL.  When a thread terminates, the variable is freed with free().
> + *
> + * The public interface to the variable is:
> + *
> + *    TYPE NAME_get(void)
> + *    TYPE NAME_get__(void)
> + *
> + *       Returns the value of per-thread variable NAME in this thread.
> + *
> + *       Use NAME_get() in a context where this might be the first use of the
> + *       per-thread variable in the program.  Use NAME_get__(), which avoids a
> + *       conditional test and is thus slightly faster, in a context where one
> + *       knows that NAME_get() has already been called previously.
> + *
> + *    TYPE NAME_set(TYPE new_value)
> + *    TYPE NAME_set__(TYPE new_value)
> + *
> + *       Sets the value of per-thread variable NAME to 'new_value' in this
> + *       thread, and returns its previous value.
> + *
> + *       Use NAME_set() in a context where this might be the first use of the
> + *       per-thread variable in the program.  Use NAME_set__(), which avoids a
> + *       conditional test and is thus slightly faster, in a context where one
> + *       knows that NAME_set() has already been called previously.
> + */
> +#define DEFINE_PER_THREAD_MALLOCED_DATA(TYPE, NAME)     \
> +    static pthread_key_t NAME##_key;                    \
> +                                                        \
> +    static void                                         \
> +    NAME##_once_init(void)                              \
> +    {                                                   \
> +        if (pthread_key_create(&NAME##_key, free)) {    \
> +            abort();                                    \
> +        }                                               \
> +    }                                                   \
> +                                                        \
> +    static void                                         \
> +    NAME##_init(void)                                   \
> +    {                                                   \
> +        static pthread_once_t once = PTHREAD_ONCE_INIT; \
> +        pthread_once(&once, NAME##_once_init);          \
> +    }                                                   \
> +                                                        \
> +    static TYPE                                         \
> +    NAME##_get__(void)                                  \
> +    {                                                   \
> +        return pthread_getspecific(NAME##_key);         \
> +    }                                                   \
> +                                                        \
> +    static OVS_UNUSED TYPE                              \
> +    NAME##_get(void)                                    \
> +    {                                                   \
> +        NAME##_init();                                  \
> +        return NAME##_get__();                          \
> +    }                                                   \
> +                                                        \
> +    static TYPE                                         \
> +    NAME##_set__(TYPE value)                            \
> +    {                                                   \
> +        TYPE old_value = NAME##_get__();                \
> +        pthread_setspecific(NAME##_key, value);         \
> +        return old_value;                               \
> +    }                                                   \
> +                                                        \
> +    static OVS_UNUSED TYPE                              \
> +    NAME##_set(TYPE value)                              \
> +    {                                                   \
> +        NAME##_init();                                  \
> +        return NAME##_set__(value);                     \
> +    }
> +
>
>  #endif /* ovs-thread.h */
> diff --git a/m4/openvswitch.m4 b/m4/openvswitch.m4
> index 12c02c0..57c71e0 100644
> --- a/m4/openvswitch.m4
> +++ b/m4/openvswitch.m4
> @@ -1,6 +1,6 @@
>  # -*- autoconf -*-
>
> -# Copyright (c) 2008, 2009, 2010, 2011, 2012 Nicira, Inc.
> +# Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013 Nicira, Inc.
>  #
>  # Licensed under the Apache License, Version 2.0 (the "License");
>  # you may not use this file except in compliance with the License.
> @@ -390,3 +390,38 @@ AC_DEFUN([OVS_CHECK_GROFF],
>         ovs_cv_groff=no
>       fi])
>     AM_CONDITIONAL([HAVE_GROFF], [test "$ovs_cv_groff" = yes])])
> +
> +dnl Checks for thread-local storage support.
> +dnl
> +dnl Checks whether the compiler and linker support the C11
> +dnl thread_local macro from <threads.h>, and if so defines
> +dnl HAVE_THREAD_LOCAL.  If not, checks whether the compiler and linker
> +dnl support the GCC __thread extension, and if so defines
> +dnl HAVE___THREAD.
> +AC_DEFUN([OVS_CHECK_TLS],
> +  [AC_CACHE_CHECK(
> +     [whether $CC has <threads.h> that supports thread_local],
> +     [ovs_cv_thread_local],
> +     [AC_LINK_IFELSE(
> +        [AC_LANG_PROGRAM([#include <threads.h>
> +static thread_local int var;], [return var;])],
> +        [ovs_cv_thread_local=yes],
> +        [ovs_cv_thread_local=no])])
> +   if test $ovs_cv_thread_local = yes; then
> +     AC_DEFINE([HAVE_THREAD_LOCAL], [1],
> +               [Define to 1 if the C compiler and linker supports the C11
> +                thread_local matcro defined in <threads.h>.])
> +   else
> +     AC_CACHE_CHECK(
> +       [whether $CC supports __thread],
> +       [ovs_cv___thread],
> +       [AC_LINK_IFELSE(
> +          [AC_LANG_PROGRAM([static __thread int var;], [return var;])],
> +          [ovs_cv___thread=yes],
> +          [ovs_cv___thread=no])])
> +     if test $ovs_cv___thread = yes; then
> +       AC_DEFINE([HAVE___THREAD], [1],
> +                 [Define to 1 if the C compiler and linker supports the
> +                  GCC __thread extenions.])
> +     fi
> +   fi])
> --
> 1.7.2.5
>
> _______________________________________________
> dev mailing list
> dev at openvswitch.org
> http://openvswitch.org/mailman/listinfo/dev



More information about the dev mailing list