mirror of
https://github.com/Alcaro/Flips.git
synced 2026-09-07 18:16:06 -05:00
Initial commit
This commit is contained in:
128
arlib/thread/atomic.h
Normal file
128
arlib/thread/atomic.h
Normal 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
250
arlib/thread/linux.cpp
Normal 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
63
arlib/thread/once.cpp
Normal 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
195
arlib/thread/pthread.cpp
Normal 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
93
arlib/thread/split.cpp
Normal 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
201
arlib/thread/thread.h
Normal 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
102
arlib/thread/win32.cpp
Normal 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
|
||||
Reference in New Issue
Block a user