diff --git a/configure.ac b/configure.ac index c0099253..72ea6ccc 100644 --- a/configure.ac +++ b/configure.ac @@ -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 diff --git a/src/common/mythread.h b/src/common/mythread.h index 476c2fc9..39641408 100644 --- a/src/common/mythread.h +++ b/src/common/mythread.h @@ -1,7 +1,7 @@ /////////////////////////////////////////////////////////////////////////////// // /// \file mythread.h -/// \brief Wrappers for threads +/// \brief Some threading related helper macros and functions // // Author: Lasse Collin // @@ -14,29 +14,189 @@ #ifdef HAVE_PTHREAD -# include -# 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 +#include +#include +#include + + +/// \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) +////////////////// +// No threading // +////////////////// -# define mythread_sigmask(how, set, oset) \ - sigprocmask(how, set, oset) +#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