commit
9f94286c9a
22 changed files with 1460 additions and 128 deletions
@ -0,0 +1,588 @@ |
||||
/* -*- mode: c; tab-width: 2; indent-tabs-mode: nil; -*-
|
||||
Copyright (c) 2012 Marcus Geelnard |
||||
|
||||
This software is provided 'as-is', without any express or implied |
||||
warranty. In no event will the authors be held liable for any damages |
||||
arising from the use of this software. |
||||
|
||||
Permission is granted to anyone to use this software for any purpose, |
||||
including commercial applications, and to alter it and redistribute it |
||||
freely, subject to the following restrictions: |
||||
|
||||
1. The origin of this software must not be misrepresented; you must not |
||||
claim that you wrote the original software. If you use this software |
||||
in a product, an acknowledgment in the product documentation would be |
||||
appreciated but is not required. |
||||
|
||||
2. Altered source versions must be plainly marked as such, and must not be |
||||
misrepresented as being the original software. |
||||
|
||||
3. This notice may not be removed or altered from any source |
||||
distribution. |
||||
*/ |
||||
|
||||
#include "tinycthread.h" |
||||
#include <stdlib.h> |
||||
|
||||
/* Platform specific includes */ |
||||
#if defined(_TTHREAD_POSIX_) |
||||
#include <signal.h> |
||||
#include <sched.h> |
||||
#include <unistd.h> |
||||
#include <sys/time.h> |
||||
#include <errno.h> |
||||
#elif defined(_TTHREAD_WIN32_) |
||||
#include <process.h> |
||||
#include <sys/timeb.h> |
||||
#endif |
||||
|
||||
/* Standard, good-to-have defines */ |
||||
#ifndef NULL |
||||
#define NULL (void*)0 |
||||
#endif |
||||
#ifndef TRUE |
||||
#define TRUE 1 |
||||
#endif |
||||
#ifndef FALSE |
||||
#define FALSE 0 |
||||
#endif |
||||
|
||||
int mtx_init(mtx_t *mtx, int type) |
||||
{ |
||||
#if defined(_TTHREAD_WIN32_) |
||||
mtx->mAlreadyLocked = FALSE; |
||||
mtx->mRecursive = type & mtx_recursive; |
||||
InitializeCriticalSection(&mtx->mHandle); |
||||
return thrd_success; |
||||
#else |
||||
int ret; |
||||
pthread_mutexattr_t attr; |
||||
pthread_mutexattr_init(&attr); |
||||
if (type & mtx_recursive) |
||||
{ |
||||
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); |
||||
} |
||||
ret = pthread_mutex_init(mtx, &attr); |
||||
pthread_mutexattr_destroy(&attr); |
||||
return ret == 0 ? thrd_success : thrd_error; |
||||
#endif |
||||
} |
||||
|
||||
void mtx_destroy(mtx_t *mtx) |
||||
{ |
||||
#if defined(_TTHREAD_WIN32_) |
||||
DeleteCriticalSection(&mtx->mHandle); |
||||
#else |
||||
pthread_mutex_destroy(mtx); |
||||
#endif |
||||
} |
||||
|
||||
int mtx_lock(mtx_t *mtx) |
||||
{ |
||||
#if defined(_TTHREAD_WIN32_) |
||||
EnterCriticalSection(&mtx->mHandle); |
||||
if (!mtx->mRecursive) |
||||
{ |
||||
while(mtx->mAlreadyLocked) Sleep(1000); /* Simulate deadlock... */ |
||||
mtx->mAlreadyLocked = TRUE; |
||||
} |
||||
return thrd_success; |
||||
#else |
||||
return pthread_mutex_lock(mtx) == 0 ? thrd_success : thrd_error; |
||||
#endif |
||||
} |
||||
|
||||
int mtx_timedlock(mtx_t *mtx, const struct timespec *ts) |
||||
{ |
||||
/* FIXME! */ |
||||
(void)mtx; |
||||
(void)ts; |
||||
return thrd_error; |
||||
} |
||||
|
||||
int mtx_trylock(mtx_t *mtx) |
||||
{ |
||||
#if defined(_TTHREAD_WIN32_) |
||||
int ret = TryEnterCriticalSection(&mtx->mHandle) ? thrd_success : thrd_busy; |
||||
if ((!mtx->mRecursive) && (ret == thrd_success) && mtx->mAlreadyLocked) |
||||
{ |
||||
LeaveCriticalSection(&mtx->mHandle); |
||||
ret = thrd_busy; |
||||
} |
||||
return ret; |
||||
#else |
||||
return (pthread_mutex_trylock(mtx) == 0) ? thrd_success : thrd_busy; |
||||
#endif |
||||
} |
||||
|
||||
int mtx_unlock(mtx_t *mtx) |
||||
{ |
||||
#if defined(_TTHREAD_WIN32_) |
||||
mtx->mAlreadyLocked = FALSE; |
||||
LeaveCriticalSection(&mtx->mHandle); |
||||
return thrd_success; |
||||
#else |
||||
return pthread_mutex_unlock(mtx) == 0 ? thrd_success : thrd_error;; |
||||
#endif |
||||
} |
||||
|
||||
#if defined(_TTHREAD_WIN32_) |
||||
#define _CONDITION_EVENT_ONE 0 |
||||
#define _CONDITION_EVENT_ALL 1 |
||||
#endif |
||||
|
||||
int cnd_init(cnd_t *cond) |
||||
{ |
||||
#if defined(_TTHREAD_WIN32_) |
||||
cond->mWaitersCount = 0; |
||||
|
||||
/* Init critical section */ |
||||
InitializeCriticalSection(&cond->mWaitersCountLock); |
||||
|
||||
/* Init events */ |
||||
cond->mEvents[_CONDITION_EVENT_ONE] = CreateEvent(NULL, FALSE, FALSE, NULL); |
||||
if (cond->mEvents[_CONDITION_EVENT_ONE] == NULL) |
||||
{ |
||||
cond->mEvents[_CONDITION_EVENT_ALL] = NULL; |
||||
return thrd_error; |
||||
} |
||||
cond->mEvents[_CONDITION_EVENT_ALL] = CreateEvent(NULL, TRUE, FALSE, NULL); |
||||
if (cond->mEvents[_CONDITION_EVENT_ALL] == NULL) |
||||
{ |
||||
CloseHandle(cond->mEvents[_CONDITION_EVENT_ONE]); |
||||
cond->mEvents[_CONDITION_EVENT_ONE] = NULL; |
||||
return thrd_error; |
||||
} |
||||
|
||||
return thrd_success; |
||||
#else |
||||
return pthread_cond_init(cond, NULL) == 0 ? thrd_success : thrd_error; |
||||
#endif |
||||
} |
||||
|
||||
void cnd_destroy(cnd_t *cond) |
||||
{ |
||||
#if defined(_TTHREAD_WIN32_) |
||||
if (cond->mEvents[_CONDITION_EVENT_ONE] != NULL) |
||||
{ |
||||
CloseHandle(cond->mEvents[_CONDITION_EVENT_ONE]); |
||||
} |
||||
if (cond->mEvents[_CONDITION_EVENT_ALL] != NULL) |
||||
{ |
||||
CloseHandle(cond->mEvents[_CONDITION_EVENT_ALL]); |
||||
} |
||||
DeleteCriticalSection(&cond->mWaitersCountLock); |
||||
#else |
||||
pthread_cond_destroy(cond); |
||||
#endif |
||||
} |
||||
|
||||
int cnd_signal(cnd_t *cond) |
||||
{ |
||||
#if defined(_TTHREAD_WIN32_) |
||||
int haveWaiters; |
||||
|
||||
/* Are there any waiters? */ |
||||
EnterCriticalSection(&cond->mWaitersCountLock); |
||||
haveWaiters = (cond->mWaitersCount > 0); |
||||
LeaveCriticalSection(&cond->mWaitersCountLock); |
||||
|
||||
/* If we have any waiting threads, send them a signal */ |
||||
if(haveWaiters) |
||||
{ |
||||
if (SetEvent(cond->mEvents[_CONDITION_EVENT_ONE]) == 0) |
||||
{ |
||||
return thrd_error; |
||||
} |
||||
} |
||||
|
||||
return thrd_success; |
||||
#else |
||||
return pthread_cond_signal(cond) == 0 ? thrd_success : thrd_error; |
||||
#endif |
||||
} |
||||
|
||||
int cnd_broadcast(cnd_t *cond) |
||||
{ |
||||
#if defined(_TTHREAD_WIN32_) |
||||
int haveWaiters; |
||||
|
||||
/* Are there any waiters? */ |
||||
EnterCriticalSection(&cond->mWaitersCountLock); |
||||
haveWaiters = (cond->mWaitersCount > 0); |
||||
LeaveCriticalSection(&cond->mWaitersCountLock); |
||||
|
||||
/* If we have any waiting threads, send them a signal */ |
||||
if(haveWaiters) |
||||
{ |
||||
if (SetEvent(cond->mEvents[_CONDITION_EVENT_ALL]) == 0) |
||||
{ |
||||
return thrd_error; |
||||
} |
||||
} |
||||
|
||||
return thrd_success; |
||||
#else |
||||
return pthread_cond_signal(cond) == 0 ? thrd_success : thrd_error; |
||||
#endif |
||||
} |
||||
|
||||
#if defined(_TTHREAD_WIN32_) |
||||
static int _cnd_timedwait_win32(cnd_t *cond, mtx_t *mtx, DWORD timeout) |
||||
{ |
||||
int result, lastWaiter; |
||||
|
||||
/* Increment number of waiters */ |
||||
EnterCriticalSection(&cond->mWaitersCountLock); |
||||
++ cond->mWaitersCount; |
||||
LeaveCriticalSection(&cond->mWaitersCountLock); |
||||
|
||||
/* Release the mutex while waiting for the condition (will decrease
|
||||
the number of waiters when done)... */ |
||||
mtx_unlock(mtx); |
||||
|
||||
/* Wait for either event to become signaled due to cnd_signal() or
|
||||
cnd_broadcast() being called */ |
||||
result = WaitForMultipleObjects(2, cond->mEvents, FALSE, timeout); |
||||
if (result == WAIT_TIMEOUT) |
||||
{ |
||||
return thrd_timeout; |
||||
} |
||||
else if (result == (int)WAIT_FAILED) |
||||
{ |
||||
return thrd_error; |
||||
} |
||||
|
||||
/* Check if we are the last waiter */ |
||||
EnterCriticalSection(&cond->mWaitersCountLock); |
||||
-- cond->mWaitersCount; |
||||
lastWaiter = (result == (WAIT_OBJECT_0 + _CONDITION_EVENT_ALL)) && |
||||
(cond->mWaitersCount == 0); |
||||
LeaveCriticalSection(&cond->mWaitersCountLock); |
||||
|
||||
/* If we are the last waiter to be notified to stop waiting, reset the event */ |
||||
if (lastWaiter) |
||||
{ |
||||
if (ResetEvent(cond->mEvents[_CONDITION_EVENT_ALL]) == 0) |
||||
{ |
||||
return thrd_error; |
||||
} |
||||
} |
||||
|
||||
/* Re-acquire the mutex */ |
||||
mtx_lock(mtx); |
||||
|
||||
return thrd_success; |
||||
} |
||||
#endif |
||||
|
||||
int cnd_wait(cnd_t *cond, mtx_t *mtx) |
||||
{ |
||||
#if defined(_TTHREAD_WIN32_) |
||||
return _cnd_timedwait_win32(cond, mtx, INFINITE); |
||||
#else |
||||
return pthread_cond_wait(cond, mtx) == 0 ? thrd_success : thrd_error; |
||||
#endif |
||||
} |
||||
|
||||
int cnd_timedwait(cnd_t *cond, mtx_t *mtx, const struct timespec *ts) |
||||
{ |
||||
#if defined(_TTHREAD_WIN32_) |
||||
struct timespec now; |
||||
if (clock_gettime(TIME_UTC, &now) == 0) |
||||
{ |
||||
DWORD delta = (ts->tv_sec - now.tv_sec) * 1000 + |
||||
(ts->tv_nsec - now.tv_nsec + 500000) / 1000000; |
||||
return _cnd_timedwait_win32(cond, mtx, delta); |
||||
} |
||||
else |
||||
return thrd_error; |
||||
#else |
||||
int ret; |
||||
ret = pthread_cond_timedwait(cond, mtx, ts); |
||||
if (ret == ETIMEDOUT) |
||||
{ |
||||
return thrd_timeout; |
||||
} |
||||
return ret == 0 ? thrd_success : thrd_error; |
||||
#endif |
||||
} |
||||
|
||||
|
||||
/** Information to pass to the new thread (what to run). */ |
||||
typedef struct { |
||||
thrd_start_t mFunction; /**< Pointer to the function to be executed. */ |
||||
void * mArg; /**< Function argument for the thread function. */ |
||||
} _thread_start_info; |
||||
|
||||
/* Thread wrapper function. */ |
||||
#if defined(_TTHREAD_WIN32_) |
||||
static unsigned WINAPI _thrd_wrapper_function(void * aArg) |
||||
#elif defined(_TTHREAD_POSIX_) |
||||
static void * _thrd_wrapper_function(void * aArg) |
||||
#endif |
||||
{ |
||||
thrd_start_t fun; |
||||
void *arg; |
||||
int res; |
||||
#if defined(_TTHREAD_POSIX_) |
||||
void *pres; |
||||
#endif |
||||
|
||||
/* Get thread startup information */ |
||||
_thread_start_info *ti = (_thread_start_info *) aArg; |
||||
fun = ti->mFunction; |
||||
arg = ti->mArg; |
||||
|
||||
/* The thread is responsible for freeing the startup information */ |
||||
free((void *)ti); |
||||
|
||||
/* Call the actual client thread function */ |
||||
res = fun(arg); |
||||
|
||||
#if defined(_TTHREAD_WIN32_) |
||||
return res; |
||||
#else |
||||
pres = malloc(sizeof(int)); |
||||
if (pres != NULL) |
||||
{ |
||||
*(int*)pres = res; |
||||
} |
||||
return pres; |
||||
#endif |
||||
} |
||||
|
||||
int thrd_create(thrd_t *thr, thrd_start_t func, void *arg) |
||||
{ |
||||
/* Fill out the thread startup information (passed to the thread wrapper,
|
||||
which will eventually free it) */ |
||||
_thread_start_info* ti = (_thread_start_info*)malloc(sizeof(_thread_start_info)); |
||||
if (ti == NULL) |
||||
{ |
||||
return thrd_nomem; |
||||
} |
||||
ti->mFunction = func; |
||||
ti->mArg = arg; |
||||
|
||||
/* Create the thread */ |
||||
#if defined(_TTHREAD_WIN32_) |
||||
*thr = (HANDLE)_beginthreadex(NULL, 0, _thrd_wrapper_function, (void *)ti, 0, NULL); |
||||
#elif defined(_TTHREAD_POSIX_) |
||||
if(pthread_create(thr, NULL, _thrd_wrapper_function, (void *)ti) != 0) |
||||
{ |
||||
*thr = 0; |
||||
} |
||||
#endif |
||||
|
||||
/* Did we fail to create the thread? */ |
||||
if(!*thr) |
||||
{ |
||||
free(ti); |
||||
return thrd_error; |
||||
} |
||||
|
||||
return thrd_success; |
||||
} |
||||
|
||||
thrd_t thrd_current(void) |
||||
{ |
||||
#if defined(_TTHREAD_WIN32_) |
||||
return GetCurrentThread(); |
||||
#else |
||||
return pthread_self(); |
||||
#endif |
||||
} |
||||
|
||||
int thrd_detach(thrd_t thr) |
||||
{ |
||||
/* FIXME! */ |
||||
(void)thr; |
||||
return thrd_error; |
||||
} |
||||
|
||||
int thrd_equal(thrd_t thr0, thrd_t thr1) |
||||
{ |
||||
#if defined(_TTHREAD_WIN32_) |
||||
return thr0 == thr1; |
||||
#else |
||||
return pthread_equal(thr0, thr1); |
||||
#endif |
||||
} |
||||
|
||||
void thrd_exit(int res) |
||||
{ |
||||
#if defined(_TTHREAD_WIN32_) |
||||
ExitThread(res); |
||||
#else |
||||
void *pres = malloc(sizeof(int)); |
||||
if (pres != NULL) |
||||
{ |
||||
*(int*)pres = res; |
||||
} |
||||
pthread_exit(pres); |
||||
#endif |
||||
} |
||||
|
||||
int thrd_join(thrd_t thr, int *res) |
||||
{ |
||||
#if defined(_TTHREAD_WIN32_) |
||||
if (WaitForSingleObject(thr, INFINITE) == WAIT_FAILED) |
||||
{ |
||||
return thrd_error; |
||||
} |
||||
if (res != NULL) |
||||
{ |
||||
DWORD dwRes; |
||||
GetExitCodeThread(thr, &dwRes); |
||||
*res = dwRes; |
||||
} |
||||
#elif defined(_TTHREAD_POSIX_) |
||||
void *pres; |
||||
int ires = 0; |
||||
if (pthread_join(thr, &pres) != 0) |
||||
{ |
||||
return thrd_error; |
||||
} |
||||
if (pres != NULL) |
||||
{ |
||||
ires = *(int*)pres; |
||||
free(pres); |
||||
} |
||||
if (res != NULL) |
||||
{ |
||||
*res = ires; |
||||
} |
||||
#endif |
||||
return thrd_success; |
||||
} |
||||
|
||||
int thrd_sleep(const struct timespec *time_point, struct timespec *remaining) |
||||
{ |
||||
struct timespec now; |
||||
#if defined(_TTHREAD_WIN32_) |
||||
DWORD delta; |
||||
#else |
||||
long delta; |
||||
#endif |
||||
|
||||
/* Get the current time */ |
||||
if (clock_gettime(TIME_UTC, &now) != 0) |
||||
return -2; // FIXME: Some specific error code?
|
||||
|
||||
#if defined(_TTHREAD_WIN32_) |
||||
/* Delta in milliseconds */ |
||||
delta = (time_point->tv_sec - now.tv_sec) * 1000 + |
||||
(time_point->tv_nsec - now.tv_nsec + 500000) / 1000000; |
||||
if (delta > 0) |
||||
{ |
||||
Sleep(delta); |
||||
} |
||||
#else |
||||
/* Delta in microseconds */ |
||||
delta = (time_point->tv_sec - now.tv_sec) * 1000000L + |
||||
(time_point->tv_nsec - now.tv_nsec + 500L) / 1000L; |
||||
|
||||
/* On some systems, the usleep argument must be < 1000000 */ |
||||
while (delta > 999999L) |
||||
{ |
||||
usleep(999999); |
||||
delta -= 999999L; |
||||
} |
||||
if (delta > 0L) |
||||
{ |
||||
usleep((useconds_t)delta); |
||||
} |
||||
#endif |
||||
|
||||
/* We don't support waking up prematurely (yet) */ |
||||
if (remaining) |
||||
{ |
||||
remaining->tv_sec = 0; |
||||
remaining->tv_nsec = 0; |
||||
} |
||||
return 0; |
||||
} |
||||
|
||||
void thrd_yield(void) |
||||
{ |
||||
#if defined(_TTHREAD_WIN32_) |
||||
Sleep(0); |
||||
#else |
||||
sched_yield(); |
||||
#endif |
||||
} |
||||
|
||||
int tss_create(tss_t *key, tss_dtor_t dtor) |
||||
{ |
||||
#if defined(_TTHREAD_WIN32_) |
||||
/* FIXME: The destructor function is not supported yet... */ |
||||
if (dtor != NULL) |
||||
{ |
||||
return thrd_error; |
||||
} |
||||
*key = TlsAlloc(); |
||||
if (*key == TLS_OUT_OF_INDEXES) |
||||
{ |
||||
return thrd_error; |
||||
} |
||||
#else |
||||
if (pthread_key_create(key, dtor) != 0) |
||||
{ |
||||
return thrd_error; |
||||
} |
||||
#endif |
||||
return thrd_success; |
||||
} |
||||
|
||||
void tss_delete(tss_t key) |
||||
{ |
||||
#if defined(_TTHREAD_WIN32_) |
||||
TlsFree(key); |
||||
#else |
||||
pthread_key_delete(key); |
||||
#endif |
||||
} |
||||
|
||||
void *tss_get(tss_t key) |
||||
{ |
||||
#if defined(_TTHREAD_WIN32_) |
||||
return TlsGetValue(key); |
||||
#else |
||||
return pthread_getspecific(key); |
||||
#endif |
||||
} |
||||
|
||||
int tss_set(tss_t key, void *val) |
||||
{ |
||||
#if defined(_TTHREAD_WIN32_) |
||||
if (TlsSetValue(key, val) == 0) |
||||
{ |
||||
return thrd_error; |
||||
} |
||||
#else |
||||
if (pthread_setspecific(key, val) != 0) |
||||
{ |
||||
return thrd_error; |
||||
} |
||||
#endif |
||||
return thrd_success; |
||||
} |
||||
|
||||
#if defined(_TTHREAD_EMULATE_CLOCK_GETTIME_) |
||||
int _tthread_clock_gettime(clockid_t clk_id, struct timespec *ts) |
||||
{ |
||||
#if defined(_TTHREAD_WIN32_) |
||||
struct _timeb tb; |
||||
_ftime(&tb); |
||||
ts->tv_sec = (time_t)tb.time; |
||||
ts->tv_nsec = 1000000L * (long)tb.millitm; |
||||
#else |
||||
struct timeval tv; |
||||
gettimeofday(&tv, NULL); |
||||
ts->tv_sec = (time_t)tv.tv_sec; |
||||
ts->tv_nsec = 1000L * (long)tv.tv_usec; |
||||
#endif |
||||
return 0; |
||||
} |
||||
#endif // _TTHREAD_EMULATE_CLOCK_GETTIME_
|
||||
|
@ -0,0 +1,437 @@ |
||||
/* -*- mode: c; tab-width: 2; indent-tabs-mode: nil; -*-
|
||||
Copyright (c) 2012 Marcus Geelnard |
||||
|
||||
This software is provided 'as-is', without any express or implied |
||||
warranty. In no event will the authors be held liable for any damages |
||||
arising from the use of this software. |
||||
|
||||
Permission is granted to anyone to use this software for any purpose, |
||||
including commercial applications, and to alter it and redistribute it |
||||
freely, subject to the following restrictions: |
||||
|
||||
1. The origin of this software must not be misrepresented; you must not |
||||
claim that you wrote the original software. If you use this software |
||||
in a product, an acknowledgment in the product documentation would be |
||||
appreciated but is not required. |
||||
|
||||
2. Altered source versions must be plainly marked as such, and must not be |
||||
misrepresented as being the original software. |
||||
|
||||
3. This notice may not be removed or altered from any source |
||||
distribution. |
||||
*/ |
||||
|
||||
#ifndef _TINYCTHREAD_H_ |
||||
#define _TINYCTHREAD_H_ |
||||
|
||||
/**
|
||||
* @file |
||||
* @mainpage TinyCThread API Reference |
||||
* |
||||
* @section intro_sec Introduction |
||||
* TinyCThread is a minimal, portable implementation of basic threading |
||||
* classes for C. |
||||
* |
||||
* They closely mimic the functionality and naming of the C11 standard, and |
||||
* should be easily replaceable with the corresponding standard variants. |
||||
* |
||||
* @section port_sec Portability |
||||
* The Win32 variant uses the native Win32 API for implementing the thread |
||||
* classes, while for other systems, the POSIX threads API (pthread) is used. |
||||
* |
||||
* @section misc_sec Miscellaneous |
||||
* The following special keywords are available: #_Thread_local. |
||||
* |
||||
* For more detailed information, browse the different sections of this |
||||
* documentation. A good place to start is: |
||||
* tinycthread.h. |
||||
*/ |
||||
|
||||
/* Which platform are we on? */ |
||||
#if !defined(_TTHREAD_PLATFORM_DEFINED_) |
||||
#if defined(_WIN32) || defined(__WIN32__) || defined(__WINDOWS__) |
||||
#define _TTHREAD_WIN32_ |
||||
#else |
||||
#define _TTHREAD_POSIX_ |
||||
#endif |
||||
#define _TTHREAD_PLATFORM_DEFINED_ |
||||
#endif |
||||
|
||||
/* Activate some POSIX functionality (e.g. clock_gettime and recursive mutexes) */ |
||||
#if defined(_TTHREAD_POSIX_) |
||||
#undef _FEATURES_H |
||||
#if !defined(_GNU_SOURCE) |
||||
#define _GNU_SOURCE |
||||
#endif |
||||
#if !defined(_POSIX_C_SOURCE) || ((_POSIX_C_SOURCE - 0) < 199309L) |
||||
#undef _POSIX_C_SOURCE |
||||
#define _POSIX_C_SOURCE 199309L |
||||
#endif |
||||
#if !defined(_XOPEN_SOURCE) || ((_XOPEN_SOURCE - 0) < 500) |
||||
#undef _XOPEN_SOURCE |
||||
#define _XOPEN_SOURCE 500 |
||||
#endif |
||||
#endif |
||||
|
||||
/* Generic includes */ |
||||
#include <time.h> |
||||
|
||||
/* Platform specific includes */ |
||||
#if defined(_TTHREAD_POSIX_) |
||||
#include <pthread.h> |
||||
#elif defined(_TTHREAD_WIN32_) |
||||
#ifndef WIN32_LEAN_AND_MEAN |
||||
#define WIN32_LEAN_AND_MEAN |
||||
#define __UNDEF_LEAN_AND_MEAN |
||||
#endif |
||||
#include <windows.h> |
||||
#ifdef __UNDEF_LEAN_AND_MEAN |
||||
#undef WIN32_LEAN_AND_MEAN |
||||
#undef __UNDEF_LEAN_AND_MEAN |
||||
#endif |
||||
#endif |
||||
|
||||
/* Workaround for missing TIME_UTC: If time.h doesn't provide TIME_UTC,
|
||||
it's quite likely that libc does not support it either. Hence, fall back to |
||||
the only other supported time specifier: CLOCK_REALTIME (and if that fails, |
||||
we're probably emulating clock_gettime anyway, so anything goes). */ |
||||
#ifndef TIME_UTC |
||||
#ifdef CLOCK_REALTIME |
||||
#define TIME_UTC CLOCK_REALTIME |
||||
#else |
||||
#define TIME_UTC 0 |
||||
#endif |
||||
#endif |
||||
|
||||
/* Workaround for missing clock_gettime (most Windows compilers, afaik) */ |
||||
#if defined(_TTHREAD_WIN32_) |
||||
#define _TTHREAD_EMULATE_CLOCK_GETTIME_ |
||||
/* Emulate struct timespec */ |
||||
struct _ttherad_timespec { |
||||
time_t tv_sec; |
||||
long tv_nsec; |
||||
}; |
||||
#define timespec _ttherad_timespec |
||||
|
||||
/* Emulate clockid_t */ |
||||
typedef int _tthread_clockid_t; |
||||
#define clockid_t _tthread_clockid_t |
||||
|
||||
/* Emulate clock_gettime */ |
||||
int _tthread_clock_gettime(clockid_t clk_id, struct timespec *ts); |
||||
#define clock_gettime _tthread_clock_gettime |
||||
#endif |
||||
|
||||
|
||||
/** TinyCThread version (major number). */ |
||||
#define TINYCTHREAD_VERSION_MAJOR 1 |
||||
/** TinyCThread version (minor number). */ |
||||
#define TINYCTHREAD_VERSION_MINOR 1 |
||||
/** TinyCThread version (full version). */ |
||||
#define TINYCTHREAD_VERSION (TINYCTHREAD_VERSION_MAJOR * 100 + TINYCTHREAD_VERSION_MINOR) |
||||
|
||||
/**
|
||||
* @def _Thread_local |
||||
* Thread local storage keyword. |
||||
* A variable that is declared with the @c _Thread_local keyword makes the |
||||
* value of the variable local to each thread (known as thread-local storage, |
||||
* or TLS). Example usage: |
||||
* @code |
||||
* // This variable is local to each thread.
|
||||
* _Thread_local int variable; |
||||
* @endcode |
||||
* @note The @c _Thread_local keyword is a macro that maps to the corresponding |
||||
* compiler directive (e.g. @c __declspec(thread)). |
||||
* @note This directive is currently not supported on Mac OS X (it will give |
||||
* a compiler error), since compile-time TLS is not supported in the Mac OS X |
||||
* executable format. Also, some older versions of MinGW (before GCC 4.x) do |
||||
* not support this directive. |
||||
* @hideinitializer |
||||
*/ |
||||
|
||||
/* FIXME: Check for a PROPER value of __STDC_VERSION__ to know if we have C11 */ |
||||
#if !(defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201102L)) && !defined(_Thread_local) |
||||
#if defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__SUNPRO_CC) || defined(__IBMCPP__) |
||||
#define _Thread_local __thread |
||||
#else |
||||
#define _Thread_local __declspec(thread) |
||||
#endif |
||||
#endif |
||||
|
||||
/* Macros */ |
||||
#define TSS_DTOR_ITERATIONS 0 |
||||
|
||||
/* Function return values */ |
||||
#define thrd_error 0 /**< The requested operation failed */ |
||||
#define thrd_success 1 /**< The requested operation succeeded */ |
||||
#define thrd_timeout 2 /**< The time specified in the call was reached without acquiring the requested resource */ |
||||
#define thrd_busy 3 /**< The requested operation failed because a tesource requested by a test and return function is already in use */ |
||||
#define thrd_nomem 4 /**< The requested operation failed because it was unable to allocate memory */ |
||||
|
||||
/* Mutex types */ |
||||
#define mtx_plain 1 |
||||
#define mtx_timed 2 |
||||
#define mtx_try 4 |
||||
#define mtx_recursive 8 |
||||
|
||||
/* Mutex */ |
||||
#if defined(_TTHREAD_WIN32_) |
||||
typedef struct { |
||||
CRITICAL_SECTION mHandle; /* Critical section handle */ |
||||
int mAlreadyLocked; /* TRUE if the mutex is already locked */ |
||||
int mRecursive; /* TRUE if the mutex is recursive */ |
||||
} mtx_t; |
||||
#else |
||||
typedef pthread_mutex_t mtx_t; |
||||
#endif |
||||
|
||||
/** Create a mutex object.
|
||||
* @param mtx A mutex object. |
||||
* @param type Bit-mask that must have one of the following six values: |
||||
* @li @c mtx_plain for a simple non-recursive mutex |
||||
* @li @c mtx_timed for a non-recursive mutex that supports timeout |
||||
* @li @c mtx_try for a non-recursive mutex that supports test and return |
||||
* @li @c mtx_plain | @c mtx_recursive (same as @c mtx_plain, but recursive) |
||||
* @li @c mtx_timed | @c mtx_recursive (same as @c mtx_timed, but recursive) |
||||
* @li @c mtx_try | @c mtx_recursive (same as @c mtx_try, but recursive) |
||||
* @return @ref thrd_success on success, or @ref thrd_error if the request could |
||||
* not be honored. |
||||
*/ |
||||
int mtx_init(mtx_t *mtx, int type); |
||||
|
||||
/** Release any resources used by the given mutex.
|
||||
* @param mtx A mutex object. |
||||
*/ |
||||
void mtx_destroy(mtx_t *mtx); |
||||
|
||||
/** Lock the given mutex.
|
||||
* Blocks until the given mutex can be locked. If the mutex is non-recursive, and |
||||
* the calling thread already has a lock on the mutex, this call will block |
||||
* forever. |
||||
* @param mtx A mutex object. |
||||
* @return @ref thrd_success on success, or @ref thrd_error if the request could |
||||
* not be honored. |
||||
*/ |
||||
int mtx_lock(mtx_t *mtx); |
||||
|
||||
/** NOT YET IMPLEMENTED.
|
||||
*/ |
||||
int mtx_timedlock(mtx_t *mtx, const struct timespec *ts); |
||||
|
||||
/** Try to lock the given mutex.
|
||||
* The specified mutex shall support either test and return or timeout. If the |
||||
* mutex is already locked, the function returns without blocking. |
||||
* @param mtx A mutex object. |
||||
* @return @ref thrd_success on success, or @ref thrd_busy if the resource |
||||
* requested is already in use, or @ref thrd_error if the request could not be |
||||
* honored. |
||||
*/ |
||||
int mtx_trylock(mtx_t *mtx); |
||||
|
||||
/** Unlock the given mutex.
|
||||
* @param mtx A mutex object. |
||||
* @return @ref thrd_success on success, or @ref thrd_error if the request could |
||||
* not be honored. |
||||
*/ |
||||
int mtx_unlock(mtx_t *mtx); |
||||
|
||||
/* Condition variable */ |
||||
#if defined(_TTHREAD_WIN32_) |
||||
typedef struct { |
||||
HANDLE mEvents[2]; /* Signal and broadcast event HANDLEs. */ |
||||
unsigned int mWaitersCount; /* Count of the number of waiters. */ |
||||
CRITICAL_SECTION mWaitersCountLock; /* Serialize access to mWaitersCount. */ |
||||
} cnd_t; |
||||
#else |
||||
typedef pthread_cond_t cnd_t; |
||||
#endif |
||||
|
||||
/** Create a condition variable object.
|
||||
* @param cond A condition variable object. |
||||
* @return @ref thrd_success on success, or @ref thrd_error if the request could |
||||
* not be honored. |
||||
*/ |
||||
int cnd_init(cnd_t *cond); |
||||
|
||||
/** Release any resources used by the given condition variable.
|
||||
* @param cond A condition variable object. |
||||
*/ |
||||
void cnd_destroy(cnd_t *cond); |
||||
|
||||
/** Signal a condition variable.
|
||||
* Unblocks one of the threads that are blocked on the given condition variable |
||||
* at the time of the call. If no threads are blocked on the condition variable |
||||
* at the time of the call, the function does nothing and return success. |
||||
* @param cond A condition variable object. |
||||
* @return @ref thrd_success on success, or @ref thrd_error if the request could |
||||
* not be honored. |
||||
*/ |
||||
int cnd_signal(cnd_t *cond); |
||||
|
||||
/** Broadcast a condition variable.
|
||||
* Unblocks all of the threads that are blocked on the given condition variable |
||||
* at the time of the call. If no threads are blocked on the condition variable |
||||
* at the time of the call, the function does nothing and return success. |
||||
* @param cond A condition variable object. |
||||
* @return @ref thrd_success on success, or @ref thrd_error if the request could |
||||
* not be honored. |
||||
*/ |
||||
int cnd_broadcast(cnd_t *cond); |
||||
|
||||
/** Wait for a condition variable to become signaled.
|
||||
* The function atomically unlocks the given mutex and endeavors to block until |
||||
* the given condition variable is signaled by a call to cnd_signal or to |
||||
* cnd_broadcast. When the calling thread becomes unblocked it locks the mutex |
||||
* before it returns. |
||||
* @param cond A condition variable object. |
||||
* @param mtx A mutex object. |
||||
* @return @ref thrd_success on success, or @ref thrd_error if the request could |
||||
* not be honored. |
||||
*/ |
||||
int cnd_wait(cnd_t *cond, mtx_t *mtx); |
||||
|
||||
/** Wait for a condition variable to become signaled.
|
||||
* The function atomically unlocks the given mutex and endeavors to block until |
||||
* the given condition variable is signaled by a call to cnd_signal or to |
||||
* cnd_broadcast, or until after the specified time. When the calling thread |
||||
* becomes unblocked it locks the mutex before it returns. |
||||
* @param cond A condition variable object. |
||||
* @param mtx A mutex object. |
||||
* @param xt A point in time at which the request will time out (absolute time). |
||||
* @return @ref thrd_success upon success, or @ref thrd_timeout if the time |
||||
* specified in the call was reached without acquiring the requested resource, or |
||||
* @ref thrd_error if the request could not be honored. |
||||
*/ |
||||
int cnd_timedwait(cnd_t *cond, mtx_t *mtx, const struct timespec *ts); |
||||
|
||||
/* Thread */ |
||||
#if defined(_TTHREAD_WIN32_) |
||||
typedef HANDLE thrd_t; |
||||
#else |
||||
typedef pthread_t thrd_t; |
||||
#endif |
||||
|
||||
/** Thread start function.
|
||||
* Any thread that is started with the @ref thrd_create() function must be |
||||
* started through a function of this type. |
||||
* @param arg The thread argument (the @c arg argument of the corresponding |
||||
* @ref thrd_create() call). |
||||
* @return The thread return value, which can be obtained by another thread |
||||
* by using the @ref thrd_join() function. |
||||
*/ |
||||
typedef int (*thrd_start_t)(void *arg); |
||||
|
||||
/** Create a new thread.
|
||||
* @param thr Identifier of the newly created thread. |
||||
* @param func A function pointer to the function that will be executed in |
||||
* the new thread. |
||||
* @param arg An argument to the thread function. |
||||
* @return @ref thrd_success on success, or @ref thrd_nomem if no memory could |
||||
* be allocated for the thread requested, or @ref thrd_error if the request |
||||
* could not be honored. |
||||
* @note A thread’s identifier may be reused for a different thread once the |
||||
* original thread has exited and either been detached or joined to another |
||||
* thread. |
||||
*/ |
||||
int thrd_create(thrd_t *thr, thrd_start_t func, void *arg); |
||||
|
||||
/** Identify the calling thread.
|
||||
* @return The identifier of the calling thread. |
||||
*/ |
||||
thrd_t thrd_current(void); |
||||
|
||||
/** NOT YET IMPLEMENTED.
|
||||
*/ |
||||
int thrd_detach(thrd_t thr); |
||||
|
||||
/** Compare two thread identifiers.
|
||||
* The function determines if two thread identifiers refer to the same thread. |
||||
* @return Zero if the two thread identifiers refer to different threads. |
||||
* Otherwise a nonzero value is returned. |
||||
*/ |
||||
int thrd_equal(thrd_t thr0, thrd_t thr1); |
||||
|
||||
/** Terminate execution of the calling thread.
|
||||
* @param res Result code of the calling thread. |
||||
*/ |
||||
void thrd_exit(int res); |
||||
|
||||
/** Wait for a thread to terminate.
|
||||
* The function joins the given thread with the current thread by blocking |
||||
* until the other thread has terminated. |
||||
* @param thr The thread to join with. |
||||
* @param res If this pointer is not NULL, the function will store the result |
||||
* code of the given thread in the integer pointed to by @c res. |
||||
* @return @ref thrd_success on success, or @ref thrd_error if the request could |
||||
* not be honored. |
||||
*/ |
||||
int thrd_join(thrd_t thr, int *res); |
||||
|
||||
/** Put the calling thread to sleep.
|
||||
* Suspend execution of the calling thread. |
||||
* @param time_point A point in time at which the thread will resume (absolute time). |
||||
* @param remaining If non-NULL, this parameter will hold the remaining time until |
||||
* time_point upon return. This will typically be zero, but if |
||||
* the thread was woken up by a signal that is not ignored before |
||||
* time_point was reached @c remaining will hold a positive |
||||
* time. |
||||
* @return 0 (zero) on successful sleep, or -1 if an interrupt occurred. |
||||
*/ |
||||
int thrd_sleep(const struct timespec *time_point, struct timespec *remaining); |
||||
|
||||
/** Yield execution to another thread.
|
||||
* Permit other threads to run, even if the current thread would ordinarily |
||||
* continue to run. |
||||
*/ |
||||
void thrd_yield(void); |
||||
|
||||
/* Thread local storage */ |
||||
#if defined(_TTHREAD_WIN32_) |
||||
typedef DWORD tss_t; |
||||
#else |
||||
typedef pthread_key_t tss_t; |
||||
#endif |
||||
|
||||
/** Destructor function for a thread-specific storage.
|
||||
* @param val The value of the destructed thread-specific storage. |
||||
*/ |
||||
typedef void (*tss_dtor_t)(void *val); |
||||
|
||||
/** Create a thread-specific storage.
|
||||
* @param key The unique key identifier that will be set if the function is |
||||
* successful. |
||||
* @param dtor Destructor function. This can be NULL. |
||||
* @return @ref thrd_success on success, or @ref thrd_error if the request could |
||||
* not be honored. |
||||
* @note The destructor function is not supported under Windows. If @c dtor is |
||||
* not NULL when calling this function under Windows, the function will fail |
||||
* and return @ref thrd_error. |
||||
*/ |
||||
int tss_create(tss_t *key, tss_dtor_t dtor); |
||||
|
||||
/** Delete a thread-specific storage.
|
||||
* The function releases any resources used by the given thread-specific |
||||
* storage. |
||||
* @param key The key that shall be deleted. |
||||
*/ |
||||
void tss_delete(tss_t key); |
||||
|
||||
/** Get the value for a thread-specific storage.
|
||||
* @param key The thread-specific storage identifier. |
||||
* @return The value for the current thread held in the given thread-specific |
||||
* storage. |
||||
*/ |
||||
void *tss_get(tss_t key); |
||||
|
||||
/** Set the value for a thread-specific storage.
|
||||
* @param key The thread-specific storage identifier. |
||||
* @param val The value of the thread-specific storage to set for the current |
||||
* thread. |
||||
* @return @ref thrd_success on success, or @ref thrd_error if the request could |
||||
* not be honored. |
||||
*/ |
||||
int tss_set(tss_t key, void *val); |
||||
|
||||
|
||||
#endif /* _TINYTHREAD_H_ */ |
||||
|
@ -0,0 +1,131 @@ |
||||
//========================================================================
|
||||
// Multithreading test
|
||||
// Copyright (c) Camilla Berglund <elmindreda@elmindreda.org>
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software.
|
||||
//
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
//
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgment in the product documentation would
|
||||
// be appreciated but is not required.
|
||||
//
|
||||
// 2. Altered source versions must be plainly marked as such, and must not
|
||||
// be misrepresented as being the original software.
|
||||
//
|
||||
// 3. This notice may not be removed or altered from any source
|
||||
// distribution.
|
||||
//
|
||||
//========================================================================
|
||||
//
|
||||
// This test is intended to verify whether the OpenGL context part of
|
||||
// the GLFW API is able to be used from multiple threads
|
||||
//
|
||||
//========================================================================
|
||||
|
||||
#include <GL/glfw3.h> |
||||
|
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <math.h> |
||||
#include <assert.h> |
||||
|
||||
#include "tinycthread.h" |
||||
|
||||
typedef struct |
||||
{ |
||||
GLFWwindow window; |
||||
const char* title; |
||||
float r, g, b; |
||||
thrd_t id; |
||||
} Thread; |
||||
|
||||
static volatile GLboolean running = GL_TRUE; |
||||
|
||||
static int thread_main(void* data) |
||||
{ |
||||
const Thread* thread = (const Thread*) data; |
||||
|
||||
glfwMakeContextCurrent(thread->window); |
||||
assert(glfwGetCurrentContext() == thread->window); |
||||
|
||||
glfwSwapInterval(1); |
||||
|
||||
while (running) |
||||
{ |
||||
const float v = (float) fabs(sin(glfwGetTime() * 2.f)); |
||||
glClearColor(thread->r * v, thread->g * v, thread->b * v, 0.f); |
||||
|
||||
glClear(GL_COLOR_BUFFER_BIT); |
||||
glfwSwapBuffers(thread->window); |
||||
} |
||||
|
||||
glfwMakeContextCurrent(NULL); |
||||
return 0; |
||||
} |
||||
|
||||
int main(void) |
||||
{ |
||||
int i, result; |
||||
Thread threads[] = |
||||
{ |
||||
{ NULL, "Red", 1.f, 0.f, 0.f, 0 }, |
||||
{ NULL, "Green", 0.f, 1.f, 0.f, 0 }, |
||||
{ NULL, "Blue", 0.f, 0.f, 1.f, 0 } |
||||
}; |
||||
const int count = sizeof(threads) / sizeof(Thread); |
||||
|
||||
if (!glfwInit()) |
||||
{ |
||||
fprintf(stderr, "Failed to initialize GLFW: %s\n", |
||||
glfwErrorString(glfwGetError())); |
||||
exit(EXIT_FAILURE); |
||||
} |
||||
|
||||
for (i = 0; i < count; i++) |
||||
{ |
||||
threads[i].window = glfwCreateWindow(200, 200, |
||||
GLFW_WINDOWED, |
||||
threads[i].title, |
||||
NULL); |
||||
if (!threads[i].window) |
||||
{ |
||||
fprintf(stderr, "Failed to open GLFW window: %s\n", |
||||
glfwErrorString(glfwGetError())); |
||||
exit(EXIT_FAILURE); |
||||
} |
||||
|
||||
glfwSetWindowPos(threads[i].window, 200 + 250 * i, 200); |
||||
|
||||
if (thrd_create(&threads[i].id, thread_main, threads + i) != |
||||
thrd_success) |
||||
{ |
||||
fprintf(stderr, "Failed to create secondary thread\n"); |
||||
exit(EXIT_FAILURE); |
||||
} |
||||
} |
||||
|
||||
while (running) |
||||
{ |
||||
assert(glfwGetCurrentContext() == NULL); |
||||
|
||||
glfwWaitEvents(); |
||||
|
||||
for (i = 0; i < count; i++) |
||||
{ |
||||
if (glfwGetWindowParam(threads[i].window, GLFW_CLOSE_REQUESTED)) |
||||
running = GL_FALSE; |
||||
} |
||||
} |
||||
|
||||
for (i = 0; i < count; i++) |
||||
thrd_join(threads[i].id, &result); |
||||
|
||||
exit(EXIT_SUCCESS); |
||||
} |
||||
|
Loading…
Reference in New Issue