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

Ben Pfaff blp at nicira.com
Wed Jun 19 20:17:03 UTC 2013


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 the
GCC extension where it is available and falls back to the POSIX API
otherwise.  (I'm not aware of any compilers that implement the C11 feature,
so this commit doesn't try to use it.)

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  |  194 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 m4/openvswitch.m4 |   21 ++++++-
 3 files changed, 215 insertions(+), 1 deletions(-)

diff --git a/configure.ac b/configure.ac
index a691963..a6c68a9 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___THREAD
 
 OVS_ENABLE_OPTION([-Wall])
 OVS_ENABLE_OPTION([-Wno-sign-compare])
diff --git a/lib/ovs-thread.h b/lib/ovs-thread.h
index cafeedf..752e44f 100644
--- a/lib/ovs-thread.h
+++ b/lib/ovs-thread.h
@@ -85,5 +85,199 @@ 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 keyword works with any data type and initializer, and it
+ *       is fast.  __thread does not require once-only initialization like
+ *       pthread_key_t.  However, __thread is GCC-specific and not portable
+ *       even to every GCC environment.  There is no provision to call a
+ *       user-specified destructor when a thread ends.
+ *
+ *     - The _Thread_local keyword is similar to __thread, but it is new in C11
+ *       and therefore not available pretty much anywhere yet.  Compared to
+ *       __thread, it has the additional disadvantage that 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.
+ *
+ * Here's a handy summary:
+ *
+ *                     pthread_key_t        __thread       _Thread_local
+ *                     -------------     -------------     -------------
+ * portability             high              medium             low
+ * speed                    low                high            high
+ * supports destructors?    yes                  no              no
+ * needs key allocation?    yes                  no              no
+ * arbitrary initializer?    no                 yes             yes
+ * cross-thread access?     yes                 yes              no
+ */
+
+/* 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, 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
+#define DEFINE_PER_THREAD_DATA(TYPE, NAME, ...)                 \
+    typedef TYPE NAME##_type;                                   \
+    static __thread 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
+#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..97aafef 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,22 @@ AC_DEFUN([OVS_CHECK_GROFF],
        ovs_cv_groff=no
      fi])
    AM_CONDITIONAL([HAVE_GROFF], [test "$ovs_cv_groff" = yes])])
+
+dnl OVS_CHECK___THREAD
+dnl
+dnl Checks whether the compiler and linker support the GCC __thread extension.
+AC_DEFUN([OVS_CHECK___THREAD],
+  [AC_CACHE_CHECK(
+     [whether $CC supports __thread],
+     [ovs_cv___thread],
+     [AC_LINK_IFELSE(
+        [AC_LANG_PROGRAM([static __thread 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 support the GCC __thread
+        extension for thread-local storage.])
+   fi])
-- 
1.7.2.5




More information about the dev mailing list