#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 start); ////Returns the number of threads to create to utilize the system resources optimally. //unsigned int thread_num_cores(); #include "atomic.h" #include //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 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 calculate); // //template T* thread_once(T* * item, function calculate) //{ // return (T*)thread_once_core((void**)item, *(function*)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 calculate, function undo); // //template T* thread_once_undo(T* * item, function calculate, function undo) //{ // return (T*)thread_once_undo_core((void**)item, // *(function*)allow_alias(&calculate), // *(function*)allow_alias(&undo)); //} // // ////This function is a workaround for a GCC bug. Don't call it yourself. //template void* thread_once_create_gccbug(void* * item) //{ // return thread_once_undo(item, bind(create), bind(undo)); //} ////Simple convenience function, just calls the above. //template T* thread_once_create(T* * item) //{ // return (T*)thread_once_create_gccbug, generic_delete_void >((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 work); //It is permitted to define this as (e.g.) QThreadStorage 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