You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
522 lines
12 KiB
522 lines
12 KiB
3 years ago
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
/// \file mythread.h
|
||
|
/// \brief Some threading related helper macros and functions
|
||
|
//
|
||
|
// Author: Lasse Collin
|
||
|
//
|
||
|
// This file has been put into the public domain.
|
||
|
// You can do whatever you want with this file.
|
||
|
//
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
#ifndef MYTHREAD_H
|
||
|
#define MYTHREAD_H
|
||
|
|
||
|
#include "sysdefs.h"
|
||
|
|
||
|
// If any type of threading is enabled, #define MYTHREAD_ENABLED.
|
||
|
#if defined(MYTHREAD_POSIX) || defined(MYTHREAD_WIN95) \
|
||
|
|| defined(MYTHREAD_VISTA)
|
||
|
# define MYTHREAD_ENABLED 1
|
||
|
#endif
|
||
|
|
||
|
|
||
|
#ifdef MYTHREAD_ENABLED
|
||
|
|
||
|
////////////////////////////////////////
|
||
|
// Shared between all threading types //
|
||
|
////////////////////////////////////////
|
||
|
|
||
|
// Locks a mutex for a duration of a block.
|
||
|
//
|
||
|
// Perform mythread_mutex_lock(&mutex) in the beginning of a block
|
||
|
// and mythread_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 mythread_mutex_lock(&mutex)
|
||
|
// and mythread_mutex_unlock(&mutex) calls.
|
||
|
//
|
||
|
#define mythread_sync(mutex) mythread_sync_helper1(mutex, __LINE__)
|
||
|
#define mythread_sync_helper1(mutex, line) mythread_sync_helper2(mutex, line)
|
||
|
#define mythread_sync_helper2(mutex, line) \
|
||
|
for (unsigned int mythread_i_ ## line = 0; \
|
||
|
mythread_i_ ## line \
|
||
|
? (mythread_mutex_unlock(&(mutex)), 0) \
|
||
|
: (mythread_mutex_lock(&(mutex)), 1); \
|
||
|
mythread_i_ ## line = 1) \
|
||
|
for (unsigned int mythread_j_ ## line = 0; \
|
||
|
!mythread_j_ ## line; \
|
||
|
mythread_j_ ## line = 1)
|
||
|
#endif
|
||
|
|
||
|
|
||
|
#if !defined(MYTHREAD_ENABLED)
|
||
|
|
||
|
//////////////////
|
||
|
// No threading //
|
||
|
//////////////////
|
||
|
|
||
|
// Calls the given function once. This isn't thread safe.
|
||
|
#define mythread_once(func) \
|
||
|
do { \
|
||
|
static bool once_ = false; \
|
||
|
if (!once_) { \
|
||
|
func(); \
|
||
|
once_ = true; \
|
||
|
} \
|
||
|
} while (0)
|
||
|
|
||
|
|
||
|
#if !(defined(_WIN32) && !defined(__CYGWIN__))
|
||
|
// Use sigprocmask() to set the signal mask in single-threaded programs.
|
||
|
#include <signal.h>
|
||
|
|
||
|
static inline void
|
||
|
mythread_sigmask(int how, const sigset_t *restrict set,
|
||
|
sigset_t *restrict oset)
|
||
|
{
|
||
|
int ret = sigprocmask(how, set, oset);
|
||
|
assert(ret == 0);
|
||
|
(void)ret;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
|
||
|
#elif defined(MYTHREAD_POSIX)
|
||
|
|
||
|
////////////////////
|
||
|
// Using pthreads //
|
||
|
////////////////////
|
||
|
|
||
|
#include <sys/time.h>
|
||
|
#include <pthread.h>
|
||
|
#include <signal.h>
|
||
|
#include <time.h>
|
||
|
#include <errno.h>
|
||
|
|
||
|
#define MYTHREAD_RET_TYPE void *
|
||
|
#define MYTHREAD_RET_VALUE NULL
|
||
|
|
||
|
typedef pthread_t mythread;
|
||
|
typedef pthread_mutex_t mythread_mutex;
|
||
|
|
||
|
typedef struct {
|
||
|
pthread_cond_t cond;
|
||
|
#ifdef HAVE_CLOCK_GETTIME
|
||
|
// Clock ID (CLOCK_REALTIME or CLOCK_MONOTONIC) associated with
|
||
|
// the condition variable.
|
||
|
clockid_t clk_id;
|
||
|
#endif
|
||
|
} mythread_cond;
|
||
|
|
||
|
typedef struct timespec mythread_condtime;
|
||
|
|
||
|
|
||
|
// Calls the given function once in a thread-safe way.
|
||
|
#define mythread_once(func) \
|
||
|
do { \
|
||
|
static pthread_once_t once_ = PTHREAD_ONCE_INIT; \
|
||
|
pthread_once(&once_, &func); \
|
||
|
} while (0)
|
||
|
|
||
|
|
||
|
// Use pthread_sigmask() to set the signal mask in multi-threaded programs.
|
||
|
// Do nothing on OpenVMS since it lacks pthread_sigmask().
|
||
|
static inline void
|
||
|
mythread_sigmask(int how, const sigset_t *restrict set,
|
||
|
sigset_t *restrict oset)
|
||
|
{
|
||
|
#ifdef __VMS
|
||
|
(void)how;
|
||
|
(void)set;
|
||
|
(void)oset;
|
||
|
#else
|
||
|
int ret = pthread_sigmask(how, set, oset);
|
||
|
assert(ret == 0);
|
||
|
(void)ret;
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
|
||
|
// Creates a new thread with all signals blocked. Returns zero on success
|
||
|
// and non-zero on error.
|
||
|
static inline int
|
||
|
mythread_create(mythread *thread, void *(*func)(void *arg), void *arg)
|
||
|
{
|
||
|
sigset_t old;
|
||
|
sigset_t all;
|
||
|
sigfillset(&all);
|
||
|
|
||
|
mythread_sigmask(SIG_SETMASK, &all, &old);
|
||
|
const int ret = pthread_create(thread, NULL, func, arg);
|
||
|
mythread_sigmask(SIG_SETMASK, &old, NULL);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
// Joins a thread. Returns zero on success and non-zero on error.
|
||
|
static inline int
|
||
|
mythread_join(mythread thread)
|
||
|
{
|
||
|
return pthread_join(thread, NULL);
|
||
|
}
|
||
|
|
||
|
|
||
|
// Initiatlizes a mutex. Returns zero on success and non-zero on error.
|
||
|
static inline int
|
||
|
mythread_mutex_init(mythread_mutex *mutex)
|
||
|
{
|
||
|
return pthread_mutex_init(mutex, NULL);
|
||
|
}
|
||
|
|
||
|
static inline void
|
||
|
mythread_mutex_destroy(mythread_mutex *mutex)
|
||
|
{
|
||
|
int ret = pthread_mutex_destroy(mutex);
|
||
|
assert(ret == 0);
|
||
|
(void)ret;
|
||
|
}
|
||
|
|
||
|
static inline void
|
||
|
mythread_mutex_lock(mythread_mutex *mutex)
|
||
|
{
|
||
|
int ret = pthread_mutex_lock(mutex);
|
||
|
assert(ret == 0);
|
||
|
(void)ret;
|
||
|
}
|
||
|
|
||
|
static inline void
|
||
|
mythread_mutex_unlock(mythread_mutex *mutex)
|
||
|
{
|
||
|
int ret = pthread_mutex_unlock(mutex);
|
||
|
assert(ret == 0);
|
||
|
(void)ret;
|
||
|
}
|
||
|
|
||
|
|
||
|
// Initializes a condition variable.
|
||
|
//
|
||
|
// 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.
|
||
|
//
|
||
|
// If clock_gettime() isn't available at all, gettimeofday() will be used.
|
||
|
static inline int
|
||
|
mythread_cond_init(mythread_cond *mycond)
|
||
|
{
|
||
|
#ifdef HAVE_CLOCK_GETTIME
|
||
|
// NOTE: HAVE_DECL_CLOCK_MONOTONIC is always defined to 0 or 1.
|
||
|
# if defined(HAVE_PTHREAD_CONDATTR_SETCLOCK) && HAVE_DECL_CLOCK_MONOTONIC
|
||
|
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.
|
||
|
// POSIX requires that all implementations of clock_gettime() must
|
||
|
// support at least CLOCK_REALTIME.
|
||
|
# endif
|
||
|
|
||
|
mycond->clk_id = CLOCK_REALTIME;
|
||
|
#endif
|
||
|
|
||
|
return pthread_cond_init(&mycond->cond, NULL);
|
||
|
}
|
||
|
|
||
|
static inline void
|
||
|
mythread_cond_destroy(mythread_cond *cond)
|
||
|
{
|
||
|
int ret = pthread_cond_destroy(&cond->cond);
|
||
|
assert(ret == 0);
|
||
|
(void)ret;
|
||
|
}
|
||
|
|
||
|
static inline void
|
||
|
mythread_cond_signal(mythread_cond *cond)
|
||
|
{
|
||
|
int ret = pthread_cond_signal(&cond->cond);
|
||
|
assert(ret == 0);
|
||
|
(void)ret;
|
||
|
}
|
||
|
|
||
|
static inline void
|
||
|
mythread_cond_wait(mythread_cond *cond, mythread_mutex *mutex)
|
||
|
{
|
||
|
int ret = pthread_cond_wait(&cond->cond, mutex);
|
||
|
assert(ret == 0);
|
||
|
(void)ret;
|
||
|
}
|
||
|
|
||
|
// Waits on a condition or until a timeout expires. If the timeout expires,
|
||
|
// non-zero is returned, otherwise zero is returned.
|
||
|
static inline int
|
||
|
mythread_cond_timedwait(mythread_cond *cond, mythread_mutex *mutex,
|
||
|
const mythread_condtime *condtime)
|
||
|
{
|
||
|
int ret = pthread_cond_timedwait(&cond->cond, mutex, condtime);
|
||
|
assert(ret == 0 || ret == ETIMEDOUT);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
// Sets condtime to the absolute time that is timeout_ms milliseconds
|
||
|
// in the future. The type of the clock to use is taken from cond.
|
||
|
static inline void
|
||
|
mythread_condtime_set(mythread_condtime *condtime, const mythread_cond *cond,
|
||
|
uint32_t timeout_ms)
|
||
|
{
|
||
|
condtime->tv_sec = timeout_ms / 1000;
|
||
|
condtime->tv_nsec = (timeout_ms % 1000) * 1000000;
|
||
|
|
||
|
#ifdef HAVE_CLOCK_GETTIME
|
||
|
struct timespec now;
|
||
|
int ret = clock_gettime(cond->clk_id, &now);
|
||
|
assert(ret == 0);
|
||
|
(void)ret;
|
||
|
|
||
|
condtime->tv_sec += now.tv_sec;
|
||
|
condtime->tv_nsec += now.tv_nsec;
|
||
|
#else
|
||
|
(void)cond;
|
||
|
|
||
|
struct timeval now;
|
||
|
gettimeofday(&now, NULL);
|
||
|
|
||
|
condtime->tv_sec += now.tv_sec;
|
||
|
condtime->tv_nsec += now.tv_usec * 1000L;
|
||
|
#endif
|
||
|
|
||
|
// tv_nsec must stay in the range [0, 999_999_999].
|
||
|
if (condtime->tv_nsec >= 1000000000L) {
|
||
|
condtime->tv_nsec -= 1000000000L;
|
||
|
++condtime->tv_sec;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
#elif defined(MYTHREAD_WIN95) || defined(MYTHREAD_VISTA)
|
||
|
|
||
|
/////////////////////
|
||
|
// Windows threads //
|
||
|
/////////////////////
|
||
|
|
||
|
#define WIN32_LEAN_AND_MEAN
|
||
|
#ifdef MYTHREAD_VISTA
|
||
|
# undef _WIN32_WINNT
|
||
|
# define _WIN32_WINNT 0x0600
|
||
|
#endif
|
||
|
#include <windows.h>
|
||
|
#include <process.h>
|
||
|
|
||
|
#define MYTHREAD_RET_TYPE unsigned int __stdcall
|
||
|
#define MYTHREAD_RET_VALUE 0
|
||
|
|
||
|
typedef HANDLE mythread;
|
||
|
typedef CRITICAL_SECTION mythread_mutex;
|
||
|
|
||
|
#ifdef MYTHREAD_WIN95
|
||
|
typedef HANDLE mythread_cond;
|
||
|
#else
|
||
|
typedef CONDITION_VARIABLE mythread_cond;
|
||
|
#endif
|
||
|
|
||
|
typedef struct {
|
||
|
// Tick count (milliseconds) in the beginning of the timeout.
|
||
|
// NOTE: This is 32 bits so it wraps around after 49.7 days.
|
||
|
// Multi-day timeouts may not work as expected.
|
||
|
DWORD start;
|
||
|
|
||
|
// Length of the timeout in milliseconds. The timeout expires
|
||
|
// when the current tick count minus "start" is equal or greater
|
||
|
// than "timeout".
|
||
|
DWORD timeout;
|
||
|
} mythread_condtime;
|
||
|
|
||
|
|
||
|
// mythread_once() is only available with Vista threads.
|
||
|
#ifdef MYTHREAD_VISTA
|
||
|
#define mythread_once(func) \
|
||
|
do { \
|
||
|
static INIT_ONCE once_ = INIT_ONCE_STATIC_INIT; \
|
||
|
BOOL pending_; \
|
||
|
if (!InitOnceBeginInitialize(&once_, 0, &pending_, NULL)) \
|
||
|
abort(); \
|
||
|
if (pending_) \
|
||
|
func(); \
|
||
|
if (!InitOnceComplete(&once, 0, NULL)) \
|
||
|
abort(); \
|
||
|
} while (0)
|
||
|
#endif
|
||
|
|
||
|
|
||
|
// mythread_sigmask() isn't available on Windows. Even a dummy version would
|
||
|
// make no sense because the other POSIX signal functions are missing anyway.
|
||
|
|
||
|
|
||
|
static inline int
|
||
|
mythread_create(mythread *thread,
|
||
|
unsigned int (__stdcall *func)(void *arg), void *arg)
|
||
|
{
|
||
|
uintptr_t ret = _beginthreadex(NULL, 0, func, arg, 0, NULL);
|
||
|
if (ret == 0)
|
||
|
return -1;
|
||
|
|
||
|
*thread = (HANDLE)ret;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static inline int
|
||
|
mythread_join(mythread thread)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
|
||
|
if (WaitForSingleObject(thread, INFINITE) != WAIT_OBJECT_0)
|
||
|
ret = -1;
|
||
|
|
||
|
if (!CloseHandle(thread))
|
||
|
ret = -1;
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
|
||
|
static inline int
|
||
|
mythread_mutex_init(mythread_mutex *mutex)
|
||
|
{
|
||
|
InitializeCriticalSection(mutex);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static inline void
|
||
|
mythread_mutex_destroy(mythread_mutex *mutex)
|
||
|
{
|
||
|
DeleteCriticalSection(mutex);
|
||
|
}
|
||
|
|
||
|
static inline void
|
||
|
mythread_mutex_lock(mythread_mutex *mutex)
|
||
|
{
|
||
|
EnterCriticalSection(mutex);
|
||
|
}
|
||
|
|
||
|
static inline void
|
||
|
mythread_mutex_unlock(mythread_mutex *mutex)
|
||
|
{
|
||
|
LeaveCriticalSection(mutex);
|
||
|
}
|
||
|
|
||
|
|
||
|
static inline int
|
||
|
mythread_cond_init(mythread_cond *cond)
|
||
|
{
|
||
|
#ifdef MYTHREAD_WIN95
|
||
|
*cond = CreateEvent(NULL, FALSE, FALSE, NULL);
|
||
|
return *cond == NULL ? -1 : 0;
|
||
|
#else
|
||
|
InitializeConditionVariable(cond);
|
||
|
return 0;
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
static inline void
|
||
|
mythread_cond_destroy(mythread_cond *cond)
|
||
|
{
|
||
|
#ifdef MYTHREAD_WIN95
|
||
|
CloseHandle(*cond);
|
||
|
#else
|
||
|
(void)cond;
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
static inline void
|
||
|
mythread_cond_signal(mythread_cond *cond)
|
||
|
{
|
||
|
#ifdef MYTHREAD_WIN95
|
||
|
SetEvent(*cond);
|
||
|
#else
|
||
|
WakeConditionVariable(cond);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
static inline void
|
||
|
mythread_cond_wait(mythread_cond *cond, mythread_mutex *mutex)
|
||
|
{
|
||
|
#ifdef MYTHREAD_WIN95
|
||
|
LeaveCriticalSection(mutex);
|
||
|
WaitForSingleObject(*cond, INFINITE);
|
||
|
EnterCriticalSection(mutex);
|
||
|
#else
|
||
|
BOOL ret = SleepConditionVariableCS(cond, mutex, INFINITE);
|
||
|
assert(ret);
|
||
|
(void)ret;
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
static inline int
|
||
|
mythread_cond_timedwait(mythread_cond *cond, mythread_mutex *mutex,
|
||
|
const mythread_condtime *condtime)
|
||
|
{
|
||
|
#ifdef MYTHREAD_WIN95
|
||
|
LeaveCriticalSection(mutex);
|
||
|
#endif
|
||
|
|
||
|
DWORD elapsed = GetTickCount() - condtime->start;
|
||
|
DWORD timeout = elapsed >= condtime->timeout
|
||
|
? 0 : condtime->timeout - elapsed;
|
||
|
|
||
|
#ifdef MYTHREAD_WIN95
|
||
|
DWORD ret = WaitForSingleObject(*cond, timeout);
|
||
|
assert(ret == WAIT_OBJECT_0 || ret == WAIT_TIMEOUT);
|
||
|
|
||
|
EnterCriticalSection(mutex);
|
||
|
|
||
|
return ret == WAIT_TIMEOUT;
|
||
|
#else
|
||
|
BOOL ret = SleepConditionVariableCS(cond, mutex, timeout);
|
||
|
assert(ret || GetLastError() == ERROR_TIMEOUT);
|
||
|
return !ret;
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
static inline void
|
||
|
mythread_condtime_set(mythread_condtime *condtime, const mythread_cond *cond,
|
||
|
uint32_t timeout)
|
||
|
{
|
||
|
(void)cond;
|
||
|
condtime->start = GetTickCount();
|
||
|
condtime->timeout = timeout;
|
||
|
}
|
||
|
|
||
|
#endif
|
||
|
|
||
|
#endif
|