Revise mythread.h.

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.
This commit is contained in:
Lasse Collin 2011-04-10 21:23:21 +03:00
parent 352ac82db5
commit 9f0a806aef
2 changed files with 180 additions and 19 deletions

View 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

View File

@ -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 <pthread.h>
# define mythread_once(func) \
do { \
////////////////////
// Using pthreads //
////////////////////
#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)
} while (0)
# define mythread_sigmask(how, set, oset) \
pthread_sigmask(how, set, oset)
/// \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 { \
//////////////////
// 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)
# define mythread_sigmask(how, set, oset) \
sigprocmask(how, set, oset)
} while (0)
#endif