Initial commit

This commit is contained in:
Alcaro
2016-07-13 18:44:41 +02:00
commit 9335f0b024
149 changed files with 107169 additions and 0 deletions

128
arlib/thread/atomic.h Normal file
View File

@@ -0,0 +1,128 @@
#pragma once
//This header defines several functions for atomically operating on integers or pointers.
//You can use int32_t, uint32_t, any typedef thereof, and any pointer.
//The following functions exist:
//lock_read(T*)
//lock_write(T*, T)
//lock_incr(T*)
//lock_decr(T*)
//lock_xchg(T*, T)
//lock_cmpxchg(T*, T, T)
//All of them use aquire-release ordering. If you know what you're doing, you can append _acq, _rel or _loose.
//All of these functions (except store) return the value before the operation.
//(cmp)xchg obviously does, so to ease memorization, the others do too.
#if GCC_VERSION > 0
#if GCC_VERSION >= 40700
//https://gcc.gnu.org/onlinedocs/gcc-4.7.0/gcc/_005f_005fatomic-Builtins.html
#define LOCKD_LOCKS_MODEL(type, model, modelname) \
inline type lock_incr ## modelname(type * val) { return __atomic_fetch_add(val, 1, model); } \
inline type lock_decr ## modelname(type * val) { return __atomic_fetch_sub(val, 1, model); } \
inline type lock_xchg ## modelname(type * val, type newval) { return __atomic_exchange_n(val, newval, model); } \
inline type lock_cmpxchg ## modelname(type * val, type old, type newval) { return __sync_val_compare_and_swap(val, old, newval); } \
//there is a modern version of cmpxchg, but it adds another move instruction for whatever reason and otherwise gives the same binary.
//__atomic_compare_exchange_n(val, &old, newval, false, __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE);
#define LOCKD_LOCKS(type) \
inline type lock_read(type * val) { return __atomic_load_n(val, __ATOMIC_ACQUIRE); } \
inline type lock_read_acq(type * val) { return __atomic_load_n(val, __ATOMIC_ACQUIRE); } \
inline type lock_read_loose(type * val) { return __atomic_load_n(val, __ATOMIC_RELAXED); } \
inline void lock_write(type * val, type newval) { __atomic_store_n(val, newval, __ATOMIC_RELEASE); } \
inline void lock_write_rel(type * val, type newval) { __atomic_store_n(val, newval, __ATOMIC_RELEASE); } \
inline void lock_write_loose(type * val, type newval) { __atomic_store_n(val, newval, __ATOMIC_RELAXED); } \
LOCKD_LOCKS_MODEL(type, __ATOMIC_ACQ_REL, ) \
LOCKD_LOCKS_MODEL(type, __ATOMIC_ACQUIRE, _acq) \
LOCKD_LOCKS_MODEL(type, __ATOMIC_RELEASE, _rel) \
LOCKD_LOCKS_MODEL(type, __ATOMIC_RELAXED, _loose) \
#else
//https://gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/Atomic-Builtins.html
//the memory model remains unused, but all functions must still be defined.
#define LOCKD_LOCKS_MODEL(type, modelname) \
inline type lock_incr ## modelname(type * val) { __sync_fetch_and_add(val, 1); } \
inline type lock_decr ## modelname(type * val) { __sync_fetch_and_sub(val, 1); } \
inline type lock_cmpxchg ## modelname(type * val, type old, type newval) { return __sync_val_compare_and_swap(val, old, newval); } \
inline type lock_xchg ## modelname(type * val, type newval) \
{ \
type prev = lock_read(val); \
while (true) \
{ \
type prev2 = lock_cmpxchg(val, prev, newval); \
if (prev == prev2) break; \
else prev = prev2; \
} \
}
#define LOCKD_LOCKS(type) \
inline type lock_read(type * val) { return __sync_fetch_and_add(val, 0); } \
inline type lock_read_acq(type * val) { return __sync_fetch_and_add(val, 0); } \
inline type lock_read_loose(type * val) { return __sync_fetch_and_add(val, 0); } \
LOCKD_LOCKS_MODEL(type, ) \
LOCKD_LOCKS_MODEL(type, _acq) \
LOCKD_LOCKS_MODEL(type, _rel) \
LOCKD_LOCKS_MODEL(type, _loose) \
inline void lock_write(type * val, type newval) { lock_xchg(val, newval); } \
inline void lock_write_rel(type * val, type newval) { lock_xchg(val, newval); } \
inline void lock_write_loose(type * val, type newval) { lock_xchg(val, newval); } \
#endif
LOCKD_LOCKS(uint32_t)
LOCKD_LOCKS(int32_t)
LOCKD_LOCKS(void*)
#elif defined(_WIN32)
#define LOCKD_LOCKS_MODEL(type, wintype, suffix, modelname) \
inline type lock_incr##modelname(type* val) { return (type)(InterlockedIncrement##suffix((wintype*)val)-1); } \
inline type lock_decr##modelname(type* val) { return (type)(InterlockedDecrement##suffix((wintype*)val)+1); } \
inline type lock_xchg##modelname(type* val, type newval) { return (type)InterlockedExchange##suffix((wintype*)val, (wintype)newval); } \
inline type lock_cmpxchg##modelname(type* val, type old, type newval) \
{ return (type)InterlockedCompareExchange##suffix((wintype*)val, (wintype)old, (wintype)newval); } \
//MSVC doesn't know what half of the memory model thingies do. Substitute in the strong ones.
#define LOCKD_LOCKS(type, wintype, suffix) \
LOCKD_LOCKS_MODEL(type, wintype, suffix, ) \
LOCKD_LOCKS_MODEL(type, wintype, suffix, _acq) \
LOCKD_LOCKS_MODEL(type, wintype, suffix, _rel) \
LOCKD_LOCKS_MODEL(type, wintype, suffix, _loose) \
\
inline type lock_read(type * val) { return (type)InterlockedCompareExchange##suffix((wintype*)val, (wintype)0, (wintype)0); } \
inline type lock_read_acq(type * val) { return (type)InterlockedCompareExchange##suffix((wintype*)val, (wintype)0, (wintype)0); } \
inline type lock_read_loose(type * val) { return (type)InterlockedCompareExchange##suffix((wintype*)val, (wintype)0, (wintype)0); } \
inline void lock_write(type * val, type value) { (void)InterlockedExchange##suffix((wintype*)val, (wintype)value); }\
inline void lock_write_rel(type * val, type value) { (void)InterlockedExchange##suffix((wintype*)val, (wintype)value); }\
inline void lock_write_loose(type * val, type value) { (void)InterlockedExchange##suffix((wintype*)val, (wintype)value); }\
#ifdef _M_IX86
LOCKD_LOCKS(int32_t, LONG, )
LOCKD_LOCKS(uint32_t, LONG, )
LOCKD_LOCKS(void*, LONG, )
#elif defined(_M_X64)
LOCKD_LOCKS(int32_t, LONG, )
LOCKD_LOCKS(uint32_t, LONG, )
LOCKD_LOCKS(void*, LONGLONG, 64)
#endif
#endif
template<typename T> T* lock_read(T** val) { return (T*)lock_read((void**)val); }
template<typename T> void lock_write(T** val, T* newval) { lock_write((void**)val, (void*)newval); }
template<typename T> T* lock_cmpxchg(T** val, T* old, T* newval) { return (T*)lock_cmpxchg((void**)val, (void*)old, (void*)newval); }
template<typename T> T* lock_xchg(T** val, T* newval) { return (T*)lock_xchg((void**)val, (void*)newval); }
#if NULL==0
//the NULL/0 duality is one of the dumbest things I have ever seen. at least C++11 somewhat fixes that garbage
class null_only;
template<typename T> void lock_write(T** val, null_only* newval) { lock_write((void**)val, NULL); }
template<typename T> T* lock_cmpxchg(T** val, null_only* old, T* newval) { return (T*)lock_cmpxchg((void**)val, NULL, (void*)newval); }
template<typename T> T* lock_cmpxchg(T** val, T* old, null_only* newval) { return (T*)lock_cmpxchg((void**)val, (void*)old, NULL); }
template<typename T> T* lock_cmpxchg(T** val, null_only* old, null_only* newval) { return (T*)lock_cmpxchg((void**)val, NULL, NULL); }
template<typename T> T* lock_xchg(T** val, null_only* newval) { return (T*)lock_xchg((void**)val, NULL); }
#endif

250
arlib/thread/linux.cpp Normal file
View File

@@ -0,0 +1,250 @@
#include "../endian.h"
#include "thread.h"
#ifdef __linux__
//I could try to rewrite all of this without pthread, but I'd rather not set up TLS stuff myself, that'd require replacing half of libc.
//However, I can remove everything except pthread_create.
//Minimum kernel version: 2.6.22 (FUTEX_PRIVATE_FLAG), released in 8 July, 2007 (source: http://kernelnewbies.org/LinuxVersions)
//Dropping the private mutex flag would drop requirements to 2.5.40, October 1, 2002.
#include <pthread.h>
#include <unistd.h>
#include <limits.h>
#include <linux/futex.h>
#include <sys/syscall.h>
#include "endian.h"
//list of synchronization points: http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap04.html#tag_04_10
struct threaddata_pthread {
function<void()> func;
};
static void * threadproc(void * userdata)
{
struct threaddata_pthread * thdat=(struct threaddata_pthread*)userdata;
thdat->func();
free(thdat);
return NULL;
}
void thread_create(function<void()> start)
{
struct threaddata_pthread * thdat=malloc(sizeof(struct threaddata_pthread));
thdat->func=start;
pthread_t thread;
if (pthread_create(&thread, NULL, threadproc, thdat)) abort();
pthread_detach(thread);
}
unsigned int thread_num_cores()
{
//for more OSes: https://qt.gitorious.org/qt/qt/source/HEAD:src/corelib/thread/qthread_unix.cpp#L411, idealThreadCount()
//or http://stackoverflow.com/questions/150355/programmatically-find-the-number-of-cores-on-a-machine
return sysconf(_SC_NPROCESSORS_ONLN);
}
void thread_sleep(unsigned int usec)
{
usleep(usec);
}
//spurious wakeups are possible
//return can tell if the wakeup is bogus, but I don't really need that
static int futex_wait(int * uaddr, int val, const struct timespec * timeout = NULL)
{
return syscall(__NR_futex, uaddr, FUTEX_WAIT_PRIVATE, val, timeout);
}
static int futex_wake(int * uaddr)
{
return syscall(__NR_futex, uaddr, FUTEX_WAKE_PRIVATE, 1);
}
static int futex_wake_all(int * uaddr)
{
return syscall(__NR_futex, uaddr, FUTEX_WAKE_PRIVATE, INT_MAX);
}
//futexes. complex threading code. fun
#define MUT_UNLOCKED 0
#define MUT_LOCKED 1
#define MUT_CONTENDED 2
void mutex::lock()
{
int result = lock_cmpxchg_acq(&fut, MUT_UNLOCKED, MUT_LOCKED);
if (LIKELY(result == MUT_UNLOCKED))
{
return; // unlocked, fast path
}
//If it was locked, mark it contended and force whoever to wake us.
//In the common contended case, it was previously MUT_LOCKED, so the futex would instantly return.
//Therefore, the xchg should be run first.
//loose is fine, since we already did an acquire above (and futex() probably performs a memory barrier).
while (true)
{
result = lock_xchg_loose(&fut, MUT_CONTENDED);
//results:
//MUT_UNLOCKED - we got it, continue
//MUT_CONTENDED - didn't get it, sleep for a while
//MUT_LOCKED - someone else got it and locked it, thinking it's empty, while we're here. force it to wake us.
if (result == MUT_UNLOCKED) break;
futex_wait(&fut, MUT_CONTENDED);
}
}
bool mutex::try_lock()
{
return (lock_cmpxchg_acq(&fut, MUT_UNLOCKED, MUT_LOCKED) == MUT_UNLOCKED);
}
void mutex::unlock()
{
int result = lock_xchg_rel(&fut, MUT_UNLOCKED);
if (UNLIKELY(result == MUT_CONTENDED))
{
futex_wake(&fut);
}
}
#define ONCE_NEW_I 0
#define ONCE_ONE_I 1
#define ONCE_CONTENDED_I 2
#define ONCE_NEW (void*)ONCE_NEW_I
#define ONCE_ONE (void*)ONCE_ONE_I
#define ONCE_CONTENDED (void*)ONCE_CONTENDED_I
//This is a fair bit shorter than the generic thread_once. And it doesn't have the objects-holding-up-each-other bug either.
//I could use Windows 8 WaitOnAddress for this, but I still (1) don't want to make 8-only binaries (2) don't have an 8.
//
//That is, it would be, if a futex was pointer rather than int. Ah well, at least it loses the bug.
void* thread_once_core(void* * item, function<void*()> calculate)
{
void* rd = *item;
//common case - initialized already
//not using an atomic read because stale values are fine, they're caught by the cmpxchg
if (rd!=ONCE_NEW && rd!=ONCE_ONE && rd!=ONCE_CONTENDED) return rd;
void* old = lock_cmpxchg(item, ONCE_NEW, ONCE_ONE);
if (old == ONCE_NEW)
{
void* result = calculate();
//'item' is either ONE or CONTENDED here.
//It's not NEW because we wrote ONE, and it can't be anything else
// because the other threads know that they're only allowed to replace it with CONTENDED.
if (lock_xchg(item, result) != ONCE_ONE)
{
futex_wake_all((ENDIAN==END_BIG)+(int*)item);
}
return result;
}
else if (old == ONCE_ONE || old == ONCE_CONTENDED)
{
lock_cmpxchg(item, ONCE_ONE, ONCE_CONTENDED);
//the timeout is necessary so we don't risk deadlocks if
//- we're on a 64bit platform
//- calculate() returns (void*)0x????????00000002 (or, on a big endian system, 0x00000002????????)
//- it's swapped in between cmpxchg(NEW->ONE) and the futex checks it
//due to the extremely low likelihood of #2, and #3 also being pretty unlikely, the timeout is
// set high (by computer standards), to 16ms.
//poking ENDIAN like that is necessary for similar reasons.
struct timespec timeout;
timeout.tv_sec = 0;
timeout.tv_nsec = 16*1000*1000;
while (true)
{
futex_wait((ENDIAN==END_BIG)+(int*)item, ONCE_CONTENDED_I, &timeout);
void* val = lock_read(item);
if (val != ONCE_CONTENDED) return val;
}
}
else return old;
}
//stuff I should rewrite follows
#include <semaphore.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
event::event()
{
this->data=malloc(sizeof(sem_t));
sem_init((sem_t*)this->data, 0, 0);
}
event::~event()
{
sem_destroy((sem_t*)this->data);
free(this->data);
}
void event::signal()
{
if (!this->signalled()) sem_post((sem_t*)this->data);
}
void event::wait()
{
sem_wait((sem_t*)this->data);
}
bool event::signalled()
{
int active;
sem_getvalue((sem_t*)this->data, &active);
return (active>0);
}
multievent::multievent()
{
this->data=malloc(sizeof(sem_t));
sem_init((sem_t*)this->data, 0, 0);
}
multievent::~multievent()
{
sem_destroy((sem_t*)this->data);
free(this->data);
}
void multievent::signal(unsigned int count)
{
while (count--) sem_post((sem_t*)this->data);
}
void multievent::wait(unsigned int count)
{
while (count--) sem_wait((sem_t*)this->data);
}
signed int multievent::count()
{
int active;
sem_getvalue((sem_t*)this->data, &active);
return active;
}
uintptr_t thread_get_id()
{
//disassembly:
//jmpq 0x400500 <pthread_self@plt>
//jmpq *0x200b22(%rip) # 0x601028 <pthread_self@got.plt>
//mov %fs:0x10,%rax
//retq
//(it's some big mess the first time, apparently the dependency is dynamically loaded)
return pthread_self();
}
#endif

63
arlib/thread/once.cpp Normal file
View File

@@ -0,0 +1,63 @@
#include "thread.h"
//a nonatomic read to an atomic variable is safe only if correct results are guaranteed if any old value is read
//a write of non-NULL and non-tag is guaranteed to be the final write, and if anything else seems to be there, we do an atomic read
void* thread_once_undo_core(void* * item, function<void*()> calculate, function<void(void*)> undo)
{
if (*item) return *item;//nonatomic - if something weird happens, all that happens is that another item is created and deleted.
void* obj = calculate();
void* prev = lock_cmpxchg(item, NULL, obj);
if (prev == NULL) return obj;
else
{
undo(obj);
return prev;
}
}
#ifndef __linux__
static event* contention_unlocker=NULL;
#if 1 //if NULL==0 and points to a permanently reserved area of at least 3 bytes (the limit is 65536 on all modern OSes)
#define MAKE_TAG(n) ((void*)(n+1))
#else //assume sizeof(obj*)>=2 - no other thread can return this, they don't know where it is
#define MAKE_TAG(n) (void*)(((char*)&contention_unlocker)+n)
#endif
#define tag_busy MAKE_TAG(0)
#define tag_contended MAKE_TAG(1)
//Bug: If two objects are simultaneously initialized by two threads each, then one of the objects may hold up the other.
//This is not fixable without borrowing at least one bit from the item, which we don't want to do; alternatively waking all waiters, which can't be done either.
void* thread_once_core(void* * item, function<void*()> calculate)
{
void* check=*item;
//common case - initialized already
//not using an atomic read because stale values are fine, they're caught by the cmpxchg
if (check != NULL && check != tag_busy && check != tag_contended) return check;
void* old = lock_cmpxchg(item, NULL, tag_busy);
if (old == NULL)
{
void* result = calculate();
//'written' is either tag_busy or tag_contended here.
//It's not NULL because we wrote tag_busy, and it can't be anything else
// because the other threads know that they're only allowed to replace it with tag_contended.
if (lock_cmpxchg(item, tag_busy, result) == tag_contended)
{
thread_once_create(&contention_unlocker);
lock_write(item, result);
contention_unlocker->signal();
}
}
else if (old == tag_busy || old == tag_contended)
{
//don't bother optimizing this, contention only happens a few times during program lifetime
lock_cmpxchg(item, tag_busy, tag_contended);
thread_once_create(&contention_unlocker);
while (lock_read(item) == tag_busy) contention_unlocker->wait();
contention_unlocker->signal();
}
//it's possible to hit neither of the above if the object was written between the initial read and the swap
return *item;
}
#endif

195
arlib/thread/pthread.cpp Normal file
View File

@@ -0,0 +1,195 @@
#include "thread.h"
#if defined(__unix__) && !defined(__linux__)
#include <pthread.h>
#include <semaphore.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
//list of synchronization points: http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap04.html#tag_04_10
struct threaddata_pthread {
function<void()> func;
};
static void * threadproc(void * userdata)
{
struct threaddata_pthread * thdat=(struct threaddata_pthread*)userdata;
thdat->func();
free(thdat);
return NULL;
}
void thread_create(function<void()> start)
{
struct threaddata_pthread * thdat=malloc(sizeof(struct threaddata_pthread));
thdat->func=start;
pthread_t thread;
if (pthread_create(&thread, NULL, threadproc, thdat)) abort();
pthread_detach(thread);
}
unsigned int thread_num_cores()
{
//for more OSes: https://qt.gitorious.org/qt/qt/source/HEAD:src/corelib/thread/qthread_unix.cpp#L411, idealThreadCount()
//or http://stackoverflow.com/questions/150355/programmatically-find-the-number-of-cores-on-a-machine
return sysconf(_SC_NPROCESSORS_ONLN);
}
mutex* mutex::create()
{
pthread_mutex_t* ret=malloc(sizeof(pthread_mutex_t));
pthread_mutex_init(ret, NULL);
return (mutex*)ret;
}
void mutex::lock()
{
pthread_mutex_lock((pthread_mutex_t*)this);
}
bool mutex::try_lock()
{
return (pthread_mutex_trylock((pthread_mutex_t*)this)==0);
}
void mutex::unlock()
{
pthread_mutex_unlock((pthread_mutex_t*)this);
}
void mutex::release()
{
pthread_mutex_destroy((pthread_mutex_t*)this);
free(this);
}
//now I have to write futex code myself! How fun!
void mutex2::lock()
{
#error not implemented yet
}
bool mutex2::try_lock()
{
}
void mutex2::unlock()
{
}
event::event()
{
this->data=malloc(sizeof(sem_t));
sem_init((sem_t*)this->data, 0, 0);
}
event::~event()
{
sem_destroy((sem_t*)this->data);
free(this->data);
}
void event::signal()
{
if (!this->signalled()) sem_post((sem_t*)this->data);
}
void event::wait()
{
sem_wait((sem_t*)this->data);
}
bool event::signalled()
{
int active;
sem_getvalue((sem_t*)this->data, &active);
return (active>0);
}
multievent::multievent()
{
this->data=malloc(sizeof(sem_t));
sem_init((sem_t*)this->data, 0, 0);
}
multievent::~multievent()
{
sem_destroy((sem_t*)this->data);
free(this->data);
}
void multievent::signal(unsigned int count)
{
while (count--) sem_post((sem_t*)this->data);
}
void multievent::wait(unsigned int count)
{
while (count--) sem_wait((sem_t*)this->data);
}
signed int multievent::count()
{
int active;
sem_getvalue((sem_t*)this->data, &active);
return active;
}
uintptr_t thread_get_id()
{
//disassembly:
//jmpq 0x400500 <pthread_self@plt>
//jmpq *0x200b22(%rip) # 0x601028 <pthread_self@got.plt>
//mov %fs:0x10,%rax
//retq
//(it's some big mess the first time, apparently the dependency is dynamically loaded)
return pthread_self();
}
//pthread doesn't seem to contain anything like this, but gcc is the only supported compiler here, so I can use its builtins.
//or if I get any non-gcc compilers, I can throw in the C++11 threads. That's why these builtins exist, anyways.
//for Clang, if these GCC builtins aren't supported (most are), http://clang.llvm.org/docs/LanguageExtensions.html#c11-atomic-builtins
#if __GNUC__*10000 + __GNUC_MINOR__*100 + __GNUC_PATCHLEVEL__*1 >= 40700
//https://gcc.gnu.org/onlinedocs/gcc-4.7.0/gcc/_005f_005fatomic-Builtins.html
uint32_t lock_incr(uint32_t * val) { return __atomic_add_fetch(val, 1, __ATOMIC_ACQ_REL); }
uint32_t lock_decr(uint32_t * val) { return __atomic_sub_fetch(val, 1, __ATOMIC_ACQ_REL); }
uint32_t lock_read(uint32_t * val) { return __atomic_load_n(val, __ATOMIC_ACQUIRE); }
void* lock_read_i(void* * val) { return __atomic_load_n(val, __ATOMIC_ACQUIRE); }
void lock_write_i(void** val, void* newval) { return __atomic_store_n(val, newval, __ATOMIC_RELEASE); }
//there is a modern version of this, but it adds another move instruction for whatever reason and otherwise gives the same binary.
void* lock_write_eq_i(void** val, void* old, void* newval) { return __sync_val_compare_and_swap(val, old, newval); }
//void* lock_write_eq_i(void** val, void* old, void* newval)
//{
// __atomic_compare_exchange_n(val, &old, newval, false, __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE);
// return old;
//}
void* lock_xchg_i(void** val, void* newval) { return __atomic_exchange_n(val, newval, __ATOMIC_ACQ_REL); }
#else
//https://gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/Atomic-Builtins.html
uint32_t lock_incr(uint32_t * val) { return __sync_add_and_fetch(val, 1); }
uint32_t lock_decr(uint32_t * val) { return __sync_sub_and_fetch(val, 1); }
uint32_t lock_read(uint32_t * val) { return __sync_val_compare_and_swap(val, 0, 0); }
inline void* lock_read_i(void* * val) { return __sync_val_compare_and_swap(val, 0, 0); }
void lock_write_i(void** val, void* newval) { *val=newval; __sync_synchronize(); }
void* lock_write_eq_i(void** val, void* old, void* newval) { return __sync_val_compare_and_swap(val, old, newval); }
//no such thing - emulate it
void* lock_xchg_i(void** val, void* newval)
{
void* prev=lock_read(val);
while (true)
{
void* prev2=lock_write_eq(val, prev, newval);
if (prev==prev2) break;
else prev=prev2;
}
}
#endif
#endif

93
arlib/thread/split.cpp Normal file
View File

@@ -0,0 +1,93 @@
#include "thread.h"
namespace {
//TODO: there is no procedure for destroying threads
struct threadpool {
mutex lock;
multievent* wake;
multievent* started;
uint32_t numthreads;
uint32_t numidle;
//these vary between each piece of work
function<void(unsigned int id)> work;
uint32_t id;
multievent* done;
};
static struct threadpool * pool;
void threadproc(struct threadpool * This)
{
while (true)
{
This->wake->wait();
lock_decr(&This->numidle);
function<void(unsigned int id)> work = This->work;
unsigned int id = lock_incr(&This->id);
multievent* done = This->done;
This->started->signal();
work(id);
done->signal();
lock_incr(&This->numidle);
}
}
struct threadpool* pool_create()
{
struct threadpool * pool = new threadpool;
pool->wake=new multievent();
pool->started=new multievent();
pool->numthreads=0;
pool->numidle=0;
return pool;
}
void pool_delete(struct threadpool* pool)
{
delete pool->wake;
delete pool->started;
delete pool;
}
}
void thread_split(unsigned int count, function<void(unsigned int id)> work)
{
if (!count) return;
if (count==1)
{
work(0);
return;
}
struct threadpool * This = thread_once_undo(&pool, bind(pool_create), bind(pool_delete));
This->lock.lock();
multievent* done=new multievent();
This->work=work;
This->id=1;
This->done=done;
while (lock_read(&This->numidle) < count-1)
{
This->numthreads++;
lock_incr(&This->numidle);
thread_create(bind_ptr(threadproc, This));
}
This->wake->signal(count-1);
This->started->wait(count-1);
This->lock.unlock();
work(0);
done->wait(count-1);
delete done;
}

201
arlib/thread/thread.h Normal file
View File

@@ -0,0 +1,201 @@
#pragma once
#include "../global.h"
#ifdef ARLIB_THREAD
//Any data associated with this thread is freed once the thread procedure returns.
//It is safe to malloc() something in one thread and free() it in another.
//It is not safe to call window_run_*() from a thread other than the one entering main().
//A thread is rather heavy; for short-running jobs, use thread_create_short or thread_split.
void thread_create(function<void()> start);
//Returns the number of threads to create to utilize the system resources optimally.
unsigned int thread_num_cores();
#include "atomic.h"
#include <string.h>
//This is a simple tool that ensures only one thread is doing a certain action at a given moment.
//Memory barriers are inserted as appropriate. Any memory access done while holding a lock is finished while holding this lock.
//This means that if all access to an object is done exclusively while holding the lock, no further synchronization is needed.
//It is not allowed for a thread to call lock() or try_lock() while holding the lock already. It is not allowed
// for a thread to release the lock unless it holds it. It is not allowed to delete the lock while it's held.
//However, it it allowed to hold multiple locks simultaneously.
//lock() is not guaranteed to yield the CPU if it can't grab the lock. It may be implemented as a
// busy loop, or a hybrid scheme that spins a few times and then sleeps.
//Remember to create all relevant mutexes before creating a thread.
class mutex : nocopy {
#if defined(__linux__)
int fut = 0;
public:
//TODO: inline fast path
void lock();
bool try_lock();
void unlock();
#elif defined(__unix__)
#error enable thread/pthread.cpp
#elif _WIN32_WINNT >= 0x0600
#if !defined(_MSC_VER) || _MSC_VER > 1600
SRWLOCK srwlock = SRWLOCK_INIT;
#else
// apparently MSVC2008 doesn't understand struct S item = {0}. let's do something it does understand and hope it's optimized out.
SRWLOCK srwlock;
public:
mutex() { srwlock.Ptr = NULL; } // and let's hope MS doesn't change the definition of RTL_SRWLOCK.
#endif
//I could define a path for Windows 8+ that uses WaitOnAddress to shrink it to one single byte, but
//(1) The more code paths, the more potential for bugs, especially the code paths I don't regularly test
//(2) Saving seven bytes is pointless, a mutex is for protecting other resources and they're bigger
//(3) Microsoft's implementation is probably better optimized
//(4) I can't test it without a machine running 8 or higher, and I don't have that.
public:
void lock() { AcquireSRWLockExclusive(&srwlock); }
bool try_lock() { return TryAcquireSRWLockExclusive(&srwlock); }
void unlock() { ReleaseSRWLockExclusive(&srwlock); }
#elif _WIN32_WINNT >= 0x0501
CRITICAL_SECTION cs;
public:
//yay, initializers. no real way to avoid them here.
mutex() { InitializeCriticalSection(&cs); }
void lock() { EnterCriticalSection(&cs); }
bool try_lock() { return TryEnterCriticalSection(&cs); }
void unlock() { LeaveCriticalSection(&cs); }
~mutex() { DeleteCriticalSection(&cs); }
#endif
};
//Some shenanigans: gcc throws errors about strict-aliasing rules if I don't force its hand, and most
// implementations aren't correctly optimized (they leave copies on the stack).
//This is one of few that confuse the optimizer exactly as much as I want.
template<typename T> char* allow_alias(T* ptr) { return (char*)ptr; }
//Executes 'calculate' exactly once. The return value is stored in 'item'. If multiple threads call
// this simultaneously, none returns until calculate() is done.
//'item' must be initialized to NULL. calculate() must return a valid pointer to an object.
// 'return new mutex;' is valid, as is returning the address of something static.
//Non-pointers, such as (void*)1, are not allowed.
//Returns *item.
void* thread_once_core(void* * item, function<void*()> calculate);
template<typename T> T* thread_once(T* * item, function<T*()> calculate)
{
return (T*)thread_once_core((void**)item, *(function<void*()>*)allow_alias(&calculate));
}
//This is like thread_once, but calculate() can be called multiple times. If this happens, undo()
//will be called for all except one; the last one will be returned.
void* thread_once_undo_core(void* * item, function<void*()> calculate, function<void(void*)> undo);
template<typename T> T* thread_once_undo(T* * item, function<T*()> calculate, function<void(T*)> undo)
{
return (T*)thread_once_undo_core((void**)item,
*(function<void*()>*)allow_alias(&calculate),
*(function<void(void*)>*)allow_alias(&undo));
}
//This function is a workaround for a GCC bug. Don't call it yourself.
template<void*(*create)(), void(*undo)(void*)> void* thread_once_create_gccbug(void* * item)
{
return thread_once_undo(item, bind(create), bind(undo));
}
//Simple convenience function, just calls the above.
template<typename T> T* thread_once_create(T* * item)
{
return (T*)thread_once_create_gccbug<generic_new_void<T>, generic_delete_void<T> >((void**)item);
}
class mutexlocker : nocopy {
mutexlocker();
mutex* m;
public:
mutexlocker(mutex* m) { this->m=m; this->m->lock(); }
~mutexlocker() { this->m->unlock(); }
};
#define synchronized(mutex) with(mutexlocker LOCK(mutex))
//This one lets one thread wake another.
//The conceptual difference between this and a mutex is that while a mutex is intended to protect a
// shared resource from being accessed simultaneously, an event is intended to wait until another
// thread is done with something. A mutex is unlocked on the same thread as it's locked; an event is
// unlocked on a different thread.
//An example would be a producer-consumer scenario; if one thread is producing 200 items per second,
// and another thread processes them at 100 items per second, then there will soon be a lot of
// waiting items. An event allows the consumer to ask the producer to get to work, so it'll spend
// half of its time sleeping, instead of filling the system memory.
//An event is boolean; calling signal() twice will drop the extra signal. It is created in the unsignalled state.
//Can be used by multiple threads, but each of signal(), wait() and signalled() should only be used by one thread.
class event : nocopy {
public:
event();
~event();
void signal();
void wait();
bool signalled();
private:
void* data;
};
//This is like event, but it allows setting the event multiple times.
class multievent {
public:
multievent();
~multievent();
//count is how many times to signal or wait. Calling it multiple times is equivalent to calling it with the sum of the arguments.
void signal(unsigned int count=1);
void wait(unsigned int count=1);
//This is how many signals are waiting to be wait()ed for. Can be below zero if something is currently waiting for this event.
//Alternate explaination: Increased for each entry to signal() and decreased for each entry to wait().
signed int count();
private:
void* data;
signed int n_count;//Not used by all implementations.
};
void thread_sleep(unsigned int usec);
//Returns a value that's unique to the current thread for as long as the process lives. Does not
// necessarily have any relationship to OS-level thread IDs, but usually is.
//This just forwards to somewhere in libc or kernel32 or something, but it's so rarely called it doesn't matter.
size_t thread_get_id();
//This one creates 'count' threads, calls work() in each of them with 'id' from 0 to 'count'-1, and
// returns once each thread has returned.
//Unlike thread_create, thread_split is expected to be called often, for short-running tasks. The threads may be reused.
//It is safe to use the values 0 and 1. However, you should avoid going above thread_ideal_count().
void thread_split(unsigned int count, function<void(unsigned int id)> work);
//It is permitted to define this as (e.g.) QThreadStorage<T> rather than compiler magic.
//However, it must support operator=(T) and operator T(), so QThreadStorage is not directly usable. A subclass may be.
//An implementation must support all stdint.h types, all basic integral types (char, short, etc), and all pointers.
#ifdef __GNUC__
#define THREAD_LOCAL(t) __thread t
#endif
#ifdef _MSC_VER
#define THREAD_LOCAL(t) __declspec(thread) t
#endif
#else
//Some parts of arlib want to work with threads disabled.
class mutex : nocopy {
public:
void lock() {}
bool try_lock() { return true; }
void unlock() { }
};
#endif

102
arlib/thread/win32.cpp Normal file
View File

@@ -0,0 +1,102 @@
#include "thread.h"
#ifdef _WIN32
#undef bind
#include <windows.h>
#define bind bind_func
#include <stdlib.h>
#include <string.h>
//list of synchronization points: http://msdn.microsoft.com/en-us/library/windows/desktop/ms686355%28v=vs.85%29.aspx
struct threaddata_win32 {
function<void()> func;
};
static DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
struct threaddata_win32 * thdat=(struct threaddata_win32*)lpParameter;
thdat->func();
free(thdat);
return 0;
}
void thread_create(function<void()> start)
{
struct threaddata_win32 * thdat=malloc(sizeof(struct threaddata_win32));
thdat->func=start;
//CreateThread is not listed as a synchronization point; it probably is, but I'd rather use a pointless
// operation than risk a really annoying bug. It's lightweight compared to creating a thread, anyways.
//MemoryBarrier();//gcc lacks this, and msvc lacks the gcc builtin I could use instead.
//And of course my gcc supports only ten out of the 137 InterlockedXxx functions. Let's pick the simplest one...
LONG ignored=0;
InterlockedIncrement(&ignored);
HANDLE h=CreateThread(NULL, 0, ThreadProc, thdat, 0, NULL);
if (!h) abort();
CloseHandle(h);
}
unsigned int thread_num_cores()
{
SYSTEM_INFO sysinf;
GetSystemInfo(&sysinf);
return sysinf.dwNumberOfProcessors;
}
void thread_sleep(unsigned int usec)
{
Sleep(usec/1000);
}
//rewritables follow
event::event() { data=(void*)CreateEvent(NULL, false, false, NULL); }
void event::signal() { SetEvent((HANDLE)this->data); }
void event::wait() { WaitForSingleObject((HANDLE)this->data, INFINITE); }
bool event::signalled() { if (WaitForSingleObject((HANDLE)this->data, 0)==WAIT_OBJECT_0) { SetEvent((HANDLE)this->data); return true; } else return false; }
event::~event() { CloseHandle((HANDLE)this->data); }
multievent::multievent()
{
this->data=(void*)CreateSemaphore(NULL, 0, 127, NULL);
this->n_count=0;
}
void multievent::signal(unsigned int count)
{
InterlockedExchangeAdd((volatile LONG*)&this->n_count, count);
ReleaseSemaphore((HANDLE)this->data, count, NULL);
}
void multievent::wait(unsigned int count)
{
InterlockedExchangeAdd((volatile LONG*)&this->n_count, -(LONG)count);
while (count)
{
WaitForSingleObject((HANDLE)this->data, INFINITE);
count--;
}
}
signed int multievent::count()
{
return InterlockedCompareExchange((volatile LONG*)&this->n_count, 0, 0);
}
multievent::~multievent() { CloseHandle((HANDLE)this->data); }
uintptr_t thread_get_id()
{
//disassembly:
//call *0x406118
//jmp 0x76c11427 <KERNEL32!GetCurrentThreadId+7>
//jmp *0x76c1085c
//mov %fs:0x10,%eax
//mov 0x24(%eax),%eax
//ret
return GetCurrentThreadId();
}
#endif