diff options
author | Kae <80987908+Novaenia@users.noreply.github.com> | 2023-06-20 14:33:09 +1000 |
---|---|---|
committer | Kae <80987908+Novaenia@users.noreply.github.com> | 2023-06-20 14:33:09 +1000 |
commit | 6352e8e3196f78388b6c771073f9e03eaa612673 (patch) | |
tree | e23772f79a7fbc41bc9108951e9e136857484bf4 /source/core/StarThread.hpp | |
parent | 6741a057e5639280d85d0f88ba26f000baa58f61 (diff) |
everything everywhere
all at once
Diffstat (limited to 'source/core/StarThread.hpp')
-rw-r--r-- | source/core/StarThread.hpp | 425 |
1 files changed, 425 insertions, 0 deletions
diff --git a/source/core/StarThread.hpp b/source/core/StarThread.hpp new file mode 100644 index 0000000..5d2c69f --- /dev/null +++ b/source/core/StarThread.hpp @@ -0,0 +1,425 @@ +#ifndef STAR_THREAD_HPP +#define STAR_THREAD_HPP + +#include "StarException.hpp" +#include "StarString.hpp" + +namespace Star { + +STAR_STRUCT(ThreadImpl); +STAR_STRUCT(ThreadFunctionImpl); +STAR_STRUCT(MutexImpl); +STAR_STRUCT(ConditionVariableImpl); +STAR_STRUCT(RecursiveMutexImpl); + +template <typename Return> +class ThreadFunction; + +class Thread { +public: + // Implementations of this method should sleep for at least the given amount + // of time, but may sleep for longer due to scheduling. + static void sleep(unsigned millis); + + // Sleep a more precise amount of time, but uses more resources to do so. + // Should be less likely to sleep much longer than the given amount of time. + static void sleepPrecise(unsigned millis); + + // Yield this thread, offering the opportunity to reschedule. + static void yield(); + + static unsigned numberOfProcessors(); + + template <typename Function, typename... Args> + static ThreadFunction<decltype(std::declval<Function>()(std::declval<Args>()...))> invoke(String const& name, Function&& f, Args&&... args); + + Thread(String const& name); + Thread(Thread&&); + // Will not automatically join! ALL implementations of this class MUST call + // join() in their most derived constructors, or not rely on the destructor + // joining. + virtual ~Thread(); + + Thread& operator=(Thread&&); + + // Start a thread that is currently in the joined state. Returns true if the + // thread was joined and is now started, false if the thread was not joined. + bool start(); + + // Wait for a thread to finish and re-join with the thread, on completion + // isJoined() will be false. Returns true if the thread was joinable, and is + // now joined, false if the thread was already joined. + bool join(); + + // Returns false when this thread been started without being joined. This is + // subtlely different than "!isRunning()", in that the thread could have + // completed its work, but a thread *must* be joined before being restarted. + bool isJoined() const; + + // Returns false before start() has been called, true immediately after + // start() has been called, and false once the run() method returns. + bool isRunning() const; + + String name(); + +protected: + virtual void run() = 0; + +private: + unique_ptr<ThreadImpl> m_impl; +}; + +// Wraps a function call and calls in another thread, very nice lightweight +// one-shot alternative to deriving from Thread. Handles exceptions in a +// different way from Thread, instead of logging the exception, the exception +// is forwarded and re-thrown during the call to finish(). +template <> +class ThreadFunction<void> { +public: + ThreadFunction(); + ThreadFunction(ThreadFunction&&); + + // Automatically starts the given function, ThreadFunction can also be + // constructed with Thread::invoke, which is a shorthand. + ThreadFunction(function<void()> function, String const& name); + + // Automatically calls finish, though BEWARE that often times this is quite + // dangerous, and this is here mostly as a fallback. The natural destructor + // order for members of a class is often wrong, and if the function throws, + // since this destructor calls finish it will throw. + ~ThreadFunction(); + + ThreadFunction& operator=(ThreadFunction&&); + + // Waits on function finish if function is assigned and started, otherwise + // does nothing. If the function threw an exception, it will be re-thrown + // here (on the first call to finish() only). + void finish(); + + // Returns whether the ThreadFunction::finish method been called and the + // ThreadFunction has stopped. Also returns true when the ThreadFunction has + // been default constructed. + bool isFinished() const; + // Returns false if the thread function has stopped running, whether or not + // finish() has been called. + bool isRunning() const; + + // Equivalent to !isFinished() + explicit operator bool() const; + + String name(); + +private: + unique_ptr<ThreadFunctionImpl> m_impl; +}; + +template <typename Return> +class ThreadFunction { +public: + ThreadFunction(); + ThreadFunction(ThreadFunction&&); + ThreadFunction(function<Return()> function, String const& name); + + ~ThreadFunction(); + + ThreadFunction& operator=(ThreadFunction&&); + + // Finishes the thread, moving and returning the final value of the function. + // If the function threw an exception, finish() will rethrow that exception. + // May only be called once, otherwise will throw InvalidMaybeAccessException. + Return finish(); + + bool isFinished() const; + bool isRunning() const; + + explicit operator bool() const; + + String name(); + +private: + ThreadFunction<void> m_function; + shared_ptr<Maybe<Return>> m_return; +}; + +// *Non* recursive mutex lock, for use with ConditionVariable +class Mutex { +public: + Mutex(); + Mutex(Mutex&&); + ~Mutex(); + + Mutex& operator=(Mutex&&); + + void lock(); + + // Attempt to acquire the mutex without blocking. + bool tryLock(); + + void unlock(); + +private: + friend struct ConditionVariableImpl; + unique_ptr<MutexImpl> m_impl; +}; + +class ConditionVariable { +public: + ConditionVariable(); + ConditionVariable(ConditionVariable&&); + ~ConditionVariable(); + + ConditionVariable& operator=(ConditionVariable&&); + + // Atomically unlocks the mutex argument and waits on the condition. On + // acquiring the condition, atomically returns and re-locks the mutex. Must + // lock the mutex before calling. If millis is given, waits for a maximum of + // the given milliseconds only. + void wait(Mutex& mutex, Maybe<unsigned> millis = {}); + + // Wake one waiting thread. The calling thread for is allowed to either hold + // or not hold the mutex that the threads waiting on the condition are using, + // both will work and result in slightly different scheduling. + void signal(); + + // Wake all threads, policy for holding the mutex is the same for signal(). + void broadcast(); + +private: + unique_ptr<ConditionVariableImpl> m_impl; +}; + +// Recursive mutex lock. lock() may be called many times freely by the same +// thread, but unlock() must be called an equal number of times to unlock it. +class RecursiveMutex { +public: + RecursiveMutex(); + RecursiveMutex(RecursiveMutex&&); + ~RecursiveMutex(); + + RecursiveMutex& operator=(RecursiveMutex&&); + + void lock(); + + // Attempt to acquire the mutex without blocking. + bool tryLock(); + + void unlock(); + +private: + unique_ptr<RecursiveMutexImpl> m_impl; +}; + +// RAII for mutexes. Locking and unlocking are always safe, MLocker will never +// attempt to lock the held mutex more than once, or unlock more than once, and +// destruction will always unlock the mutex *iff* it is actually locked. +// (Locked here refers to one specific MLocker *itself* locking the mutex, not +// whether the mutex is locked *at all*, so it is sensible to use with +// RecursiveMutex) +template <typename MutexType> +class MLocker { +public: + // Pass false to lock to start unlocked + MLocker(MutexType& ref, bool lock = true); + ~MLocker(); + + MLocker(MLocker const&) = delete; + MLocker& operator=(MLocker const&) = delete; + + MutexType& mutex(); + + void unlock(); + void lock(); + bool tryLock(); + +private: + MutexType& m_mutex; + bool m_locked; +}; +typedef MLocker<Mutex> MutexLocker; +typedef MLocker<RecursiveMutex> RecursiveMutexLocker; + +class ReadersWriterMutex { +public: + ReadersWriterMutex(); + + void readLock(); + bool tryReadLock(); + void readUnlock(); + + void writeLock(); + bool tryWriteLock(); + void writeUnlock(); + +private: + Mutex m_mutex; + ConditionVariable m_readCond; + ConditionVariable m_writeCond; + unsigned m_readers; + unsigned m_writers; + unsigned m_readWaiters; + unsigned m_writeWaiters; +}; + +class ReadLocker { +public: + ReadLocker(ReadersWriterMutex& rwlock, bool startLocked = true); + ~ReadLocker(); + + ReadLocker(ReadLocker const&) = delete; + ReadLocker& operator=(ReadLocker const&) = delete; + + void unlock(); + void lock(); + bool tryLock(); + +private: + ReadersWriterMutex& m_lock; + bool m_locked; +}; + +class WriteLocker { +public: + WriteLocker(ReadersWriterMutex& rwlock, bool startLocked = true); + ~WriteLocker(); + + WriteLocker(WriteLocker const&) = delete; + WriteLocker& operator=(WriteLocker const&) = delete; + + void unlock(); + void lock(); + bool tryLock(); + +private: + ReadersWriterMutex& m_lock; + bool m_locked; +}; + +class SpinLock { +public: + SpinLock(); + + void lock(); + bool tryLock(); + void unlock(); + +private: + atomic_flag m_lock; +}; +typedef MLocker<SpinLock> SpinLocker; + +template <typename MutexType> +MLocker<MutexType>::MLocker(MutexType& ref, bool l) + : m_mutex(ref), m_locked(false) { + if (l) + lock(); +} + +template <typename MutexType> +MLocker<MutexType>::~MLocker() { + unlock(); +} + +template <typename MutexType> +MutexType& MLocker<MutexType>::mutex() { + return m_mutex; +} + +template <typename MutexType> +void MLocker<MutexType>::unlock() { + if (m_locked) { + m_mutex.unlock(); + m_locked = false; + } +} + +template <typename MutexType> +void MLocker<MutexType>::lock() { + if (!m_locked) { + m_mutex.lock(); + m_locked = true; + } +} + +template <typename MutexType> +bool MLocker<MutexType>::tryLock() { + if (!m_locked) { + if (m_mutex.tryLock()) + m_locked = true; + } + + return m_locked; +} + +template <typename Function, typename... Args> +ThreadFunction<decltype(std::declval<Function>()(std::declval<Args>()...))> Thread::invoke(String const& name, Function&& f, Args&&... args) { + return {bind(forward<Function>(f), forward<Args>(args)...), name}; +} + +template <typename Return> +ThreadFunction<Return>::ThreadFunction() {} + +template <typename Return> +ThreadFunction<Return>::ThreadFunction(ThreadFunction&&) = default; + +template <typename Return> +ThreadFunction<Return>::ThreadFunction(function<Return()> function, String const& name) { + m_return = make_shared<Maybe<Return>>(); + m_function = ThreadFunction<void>([function = move(function), retValue = m_return]() { + *retValue = function(); + }, name); +} + +template <typename Return> +ThreadFunction<Return>::~ThreadFunction() { + m_function.finish(); +} + +template <typename Return> +ThreadFunction<Return>& ThreadFunction<Return>::operator=(ThreadFunction&&) = default; + +template <typename Return> +Return ThreadFunction<Return>::finish() { + m_function.finish(); + return m_return->take(); +} + +template <typename Return> +bool ThreadFunction<Return>::isFinished() const { + return m_function.isFinished(); +} + +template <typename Return> +bool ThreadFunction<Return>::isRunning() const { + return m_function.isRunning(); +} + +template <typename Return> +ThreadFunction<Return>::operator bool() const { + return !isFinished(); +} + +template <typename Return> +String ThreadFunction<Return>::name() { + return m_function.name(); +} + +inline SpinLock::SpinLock() { + m_lock.clear(); +} + +inline void SpinLock::lock() { + while (m_lock.test_and_set(std::memory_order_acquire)) + ; +} + +inline void SpinLock::unlock() { + m_lock.clear(std::memory_order_release); +} + +inline bool SpinLock::tryLock() { + return !m_lock.test_and_set(std::memory_order_acquire); +} + +} + +#endif |