Revise mythread.h.
authorLasse Collin <lasse.collin@tukaani.org>
Sun, 10 Apr 2011 18:23:21 +0000 (21:23 +0300)
committerLasse Collin <lasse.collin@tukaani.org>
Sun, 10 Apr 2011 18:23:21 +0000 (21:23 +0300)
This adds:

  - mythread_sync() macro to create synchronized blocks

  - mythread_cond structure and related functions
    and macros for condition variables with timed
    waiting using a relative timeout

  - mythread_create() to create a thread with all
    signals blocked

Some of these wouldn't need to be inline functions,
but I'll keep them this way for now for simplicity.

For timed waiting on a condition variable, librt is
now required on some systems to use clock_gettime().
configure.ac was updated to handle this.

configure.ac
src/common/mythread.h

index c009925..72ea6cc 100644 (file)
@@ -435,6 +435,7 @@ if test "x$enable_threads" = xyes; then
        LIBS="$LIBS $PTHREAD_LIBS"
        AM_CFLAGS="$AM_CFLAGS $PTHREAD_CFLAGS"
        CC="$PTHREAD_CC"
+       AC_SEARCH_LIBS([clock_gettime], [rt])
 fi
 
 echo
index 476c2fc..3964140 100644 (file)
@@ -1,7 +1,7 @@
 ///////////////////////////////////////////////////////////////////////////////
 //
 /// \file       mythread.h
-/// \brief      Wrappers for threads
+/// \brief      Some threading related helper macros and functions
 //
 //  Author:     Lasse Collin
 //
 
 
 #ifdef HAVE_PTHREAD
-#      include <pthread.h>
 
-#      define mythread_once(func) \
-       do { \
-               static pthread_once_t once_ = PTHREAD_ONCE_INIT; \
-               pthread_once(&once_, &func); \
-       } while (0)
+////////////////////
+// Using pthreads //
+////////////////////
 
-#      define mythread_sigmask(how, set, oset) \
-               pthread_sigmask(how, set, oset)
+#include <pthread.h>
+#include <signal.h>
+#include <time.h>
+#include <unistd.h>
+
+
+/// \brief      Set the process signal mask
+///
+/// If threads are disabled, sigprocmask() is used instead
+/// of pthread_sigmask().
+#define mythread_sigmask(how, set, oset) \
+       pthread_sigmask(how, set, oset)
+
+
+/// \brief      Call the given function once
+///
+/// If threads are disabled, a thread-unsafe version is used.
+#define mythread_once(func) \
+do { \
+       static pthread_once_t once_ = PTHREAD_ONCE_INIT; \
+       pthread_once(&once_, &func); \
+} while (0)
+
+
+/// \brief      Lock a mutex for a duration of a block
+///
+/// Perform pthread_mutex_lock(&mutex) in the beginning of a block
+/// and pthread_mutex_unlock(&mutex) at the end of the block. "break"
+/// may be used to unlock the mutex and jump out of the block.
+/// mythread_sync blocks may be nested.
+///
+/// Example:
+///
+///     mythread_sync(mutex) {
+///         foo();
+///         if (some_error)
+///             break; // Skips bar()
+///         bar();
+///     }
+///
+/// At least GCC optimizes the loops completely away so it doesn't slow
+/// things down at all compared to plain pthread_mutex_lock(&mutex)
+/// and pthread_mutex_unlock(&mutex) calls.
+///
+#define mythread_sync(mutex) mythread_sync_helper(mutex, __LINE__)
+#define mythread_sync_helper(mutex, line) \
+       for (unsigned int mythread_i_ ## line = 0; \
+                       mythread_i_ ## line \
+                               ? (pthread_mutex_unlock(&(mutex)), 0) \
+                               : (pthread_mutex_lock(&(mutex)), 1); \
+                       mythread_i_ ## line = 1) \
+               for (unsigned int mythread_j_ ## line = 0; \
+                               !mythread_j_ ## line; \
+                               mythread_j_ ## line = 1)
+
+
+typedef struct {
+       /// Condition variable
+       pthread_cond_t cond;
+
+       /// Clock ID (CLOCK_REALTIME or CLOCK_MONOTONIC) associated with
+       /// the condition variable
+       clockid_t clk_id;
+
+} mythread_cond;
+
+
+/// \brief      Initialize a condition variable to use CLOCK_MONOTONIC
+///
+/// Using CLOCK_MONOTONIC instead of the default CLOCK_REALTIME makes the
+/// timeout in pthread_cond_timedwait() work correctly also if system time
+/// is suddenly changed. Unfortunately CLOCK_MONOTONIC isn't available
+/// everywhere while the default CLOCK_REALTIME is, so the default is
+/// used if CLOCK_MONOTONIC isn't available.
+static inline int
+mythread_cond_init(mythread_cond *mycond)
+{
+#if defined(_POSIX_CLOCK_SELECTION) && defined(_POSIX_MONOTONIC_CLOCK)
+       struct timespec ts;
+       pthread_condattr_t condattr;
+
+       // POSIX doesn't seem to *require* that pthread_condattr_setclock()
+       // will fail if given an unsupported clock ID. Test that
+       // CLOCK_MONOTONIC really is supported using clock_gettime().
+       if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0
+                       && pthread_condattr_init(&condattr) == 0) {
+               int ret = pthread_condattr_setclock(
+                               &condattr, CLOCK_MONOTONIC);
+               if (ret == 0)
+                       ret = pthread_cond_init(&mycond->cond, &condattr);
+
+               pthread_condattr_destroy(&condattr);
+
+               if (ret == 0) {
+                       mycond->clk_id = CLOCK_MONOTONIC;
+                       return 0;
+               }
+       }
+
+       // If anything above fails, fall back to the default CLOCK_REALTIME.
+#endif
+
+       mycond->clk_id = CLOCK_REALTIME;
+       return pthread_cond_init(&mycond->cond, NULL);
+}
+
+
+/// \brief      Convert relative time to absolute time for use with timed wait
+///
+/// The current time of the clock associated with the condition variable
+/// is added to the relative time in *ts.
+static inline void
+mythread_cond_abstime(const mythread_cond *mycond, struct timespec *ts)
+{
+       struct timespec now;
+       clock_gettime(mycond->clk_id, &now);
+
+       ts->tv_sec += now.tv_sec;
+       ts->tv_nsec += now.tv_nsec;
+
+       // tv_nsec must stay in the range [0, 999_999_999].
+       if (ts->tv_nsec >= 1000000000L) {
+               ts->tv_nsec -= 1000000000L;
+               ++ts->tv_sec;
+       }
+
+       return;
+}
+
+
+#define mythread_cond_wait(mycondptr, mutexptr) \
+       pthread_cond_wait(&(mycondptr)->cond, mutexptr)
+
+#define mythread_cond_timedwait(mycondptr, mutexptr, abstimeptr) \
+       pthread_cond_timedwait(&(mycondptr)->cond, mutexptr, abstimeptr)
+
+#define mythread_cond_signal(mycondptr) \
+       pthread_cond_signal(&(mycondptr)->cond)
+
+#define mythread_cond_broadcast(mycondptr) \
+       pthread_cond_broadcast(&(mycondptr)->cond)
+
+#define mythread_cond_destroy(mycondptr) \
+       pthread_cond_destroy(&(mycondptr)->cond)
+
+
+/// \brief      Create a thread with all signals blocked
+static inline int
+mythread_create(pthread_t *thread, void *(*func)(void *arg), void *arg)
+{
+       sigset_t old;
+       sigset_t all;
+       sigfillset(&all);
+
+       pthread_sigmask(SIG_SETMASK, &all, &old);
+       const int ret = pthread_create(thread, NULL, func, arg);
+       pthread_sigmask(SIG_SETMASK, &old, NULL);
+
+       return ret;
+}
 
 #else
 
-#      define mythread_once(func) \
-       do { \
-               static bool once_ = false; \
-               if (!once_) { \
-                       func(); \
-                       once_ = true; \
-               } \
-       } while (0)
-
-#      define mythread_sigmask(how, set, oset) \
-               sigprocmask(how, set, oset)
+//////////////////
+// No threading //
+//////////////////
+
+#define mythread_sigmask(how, set, oset) \
+       sigprocmask(how, set, oset)
+
+
+#define mythread_once(func) \
+do { \
+       static bool once_ = false; \
+       if (!once_) { \
+               func(); \
+               once_ = true; \
+       } \
+} while (0)
 
 #endif