| @@ -816,31 +816,31 @@ extern "C" void* threadEntryProc (void* userData) | |||||
| void Thread::launchThread() | void Thread::launchThread() | ||||
| { | { | ||||
| threadHandle_ = 0; | |||||
| threadHandle = 0; | |||||
| pthread_t handle = 0; | pthread_t handle = 0; | ||||
| if (pthread_create (&handle, 0, threadEntryProc, this) == 0) | if (pthread_create (&handle, 0, threadEntryProc, this) == 0) | ||||
| { | { | ||||
| pthread_detach (handle); | pthread_detach (handle); | ||||
| threadHandle_ = (void*) handle; | |||||
| threadId_ = (ThreadID) threadHandle_; | |||||
| threadHandle = (void*) handle; | |||||
| threadId = (ThreadID) threadHandle; | |||||
| } | } | ||||
| } | } | ||||
| void Thread::closeThreadHandle() | void Thread::closeThreadHandle() | ||||
| { | { | ||||
| threadId_ = 0; | |||||
| threadHandle_ = 0; | |||||
| threadId = 0; | |||||
| threadHandle = 0; | |||||
| } | } | ||||
| void Thread::killThread() | void Thread::killThread() | ||||
| { | { | ||||
| if (threadHandle_ != 0) | |||||
| if (threadHandle != 0) | |||||
| { | { | ||||
| #if JUCE_ANDROID | #if JUCE_ANDROID | ||||
| jassertfalse; // pthread_cancel not available! | jassertfalse; // pthread_cancel not available! | ||||
| #else | #else | ||||
| pthread_cancel ((pthread_t) threadHandle_); | |||||
| pthread_cancel ((pthread_t) threadHandle); | |||||
| #endif | #endif | ||||
| } | } | ||||
| } | } | ||||
| @@ -125,25 +125,25 @@ static unsigned int __stdcall threadEntryProc (void* userData) | |||||
| void Thread::launchThread() | void Thread::launchThread() | ||||
| { | { | ||||
| unsigned int newThreadId; | unsigned int newThreadId; | ||||
| threadHandle_ = (void*) _beginthreadex (0, 0, &threadEntryProc, this, 0, &newThreadId); | |||||
| threadId_ = (ThreadID) newThreadId; | |||||
| threadHandle = (void*) _beginthreadex (0, 0, &threadEntryProc, this, 0, &newThreadId); | |||||
| threadId = (ThreadID) newThreadId; | |||||
| } | } | ||||
| void Thread::closeThreadHandle() | void Thread::closeThreadHandle() | ||||
| { | { | ||||
| CloseHandle ((HANDLE) threadHandle_); | |||||
| threadId_ = 0; | |||||
| threadHandle_ = 0; | |||||
| CloseHandle ((HANDLE) threadHandle); | |||||
| threadId = 0; | |||||
| threadHandle = 0; | |||||
| } | } | ||||
| void Thread::killThread() | void Thread::killThread() | ||||
| { | { | ||||
| if (threadHandle_ != 0) | |||||
| if (threadHandle != 0) | |||||
| { | { | ||||
| #if JUCE_DEBUG | #if JUCE_DEBUG | ||||
| OutputDebugString (_T("** Warning - Forced thread termination **\n")); | |||||
| OutputDebugStringA ("** Warning - Forced thread termination **\n"); | |||||
| #endif | #endif | ||||
| TerminateThread (threadHandle_, 0); | |||||
| TerminateThread (threadHandle, 0); | |||||
| } | } | ||||
| } | } | ||||
| @@ -23,119 +23,34 @@ | |||||
| ============================================================================== | ============================================================================== | ||||
| */ | */ | ||||
| class RunningThreadsList | |||||
| static ThreadLocalValue<Thread*>& getCurrentThreadHolder() | |||||
| { | { | ||||
| public: | |||||
| RunningThreadsList() | |||||
| { | |||||
| } | |||||
| ~RunningThreadsList() | |||||
| { | |||||
| // Some threads are still running! Make sure you stop all your | |||||
| // threads cleanly before your app quits! | |||||
| jassert (threads.size() == 0); | |||||
| } | |||||
| void add (Thread* const thread) | |||||
| { | |||||
| const SpinLock::ScopedLockType sl (lock); | |||||
| jassert (! threads.contains (thread)); | |||||
| threads.add (thread); | |||||
| } | |||||
| void remove (Thread* const thread) | |||||
| { | |||||
| const SpinLock::ScopedLockType sl (lock); | |||||
| jassert (threads.contains (thread)); | |||||
| threads.removeValue (thread); | |||||
| } | |||||
| int size() const noexcept | |||||
| { | |||||
| return threads.size(); | |||||
| } | |||||
| Thread* getThreadWithID (const Thread::ThreadID targetID) const noexcept | |||||
| { | |||||
| const SpinLock::ScopedLockType sl (lock); | |||||
| for (int i = threads.size(); --i >= 0;) | |||||
| { | |||||
| Thread* const t = threads.getUnchecked(i); | |||||
| if (t->getThreadId() == targetID) | |||||
| return t; | |||||
| } | |||||
| return nullptr; | |||||
| } | |||||
| void stopAll (const int timeOutMilliseconds) | |||||
| { | |||||
| signalAllThreadsToStop(); | |||||
| for (;;) | |||||
| { | |||||
| Thread* firstThread = getFirstThread(); | |||||
| if (firstThread != nullptr) | |||||
| firstThread->stopThread (timeOutMilliseconds); | |||||
| else | |||||
| break; | |||||
| } | |||||
| } | |||||
| static RunningThreadsList& getInstance() | |||||
| { | |||||
| static RunningThreadsList runningThreads; | |||||
| return runningThreads; | |||||
| } | |||||
| private: | |||||
| Array<Thread*> threads; | |||||
| SpinLock lock; | |||||
| void signalAllThreadsToStop() | |||||
| { | |||||
| const SpinLock::ScopedLockType sl (lock); | |||||
| for (int i = threads.size(); --i >= 0;) | |||||
| threads.getUnchecked(i)->signalThreadShouldExit(); | |||||
| } | |||||
| Thread* getFirstThread() const | |||||
| { | |||||
| const SpinLock::ScopedLockType sl (lock); | |||||
| return threads.getFirst(); | |||||
| } | |||||
| }; | |||||
| static ThreadLocalValue<Thread*> tls; | |||||
| return tls; | |||||
| } | |||||
| //============================================================================== | |||||
| void Thread::threadEntryPoint() | void Thread::threadEntryPoint() | ||||
| { | { | ||||
| RunningThreadsList::getInstance().add (this); | |||||
| getCurrentThreadHolder() = this; | |||||
| JUCE_TRY | JUCE_TRY | ||||
| { | { | ||||
| if (threadName_.isNotEmpty()) | |||||
| setCurrentThreadName (threadName_); | |||||
| if (threadName.isNotEmpty()) | |||||
| setCurrentThreadName (threadName); | |||||
| if (startSuspensionEvent_.wait (10000)) | |||||
| if (startSuspensionEvent.wait (10000)) | |||||
| { | { | ||||
| jassert (getCurrentThreadId() == threadId_); | |||||
| jassert (getCurrentThreadId() == threadId); | |||||
| if (affinityMask_ != 0) | |||||
| setCurrentThreadAffinityMask (affinityMask_); | |||||
| if (affinityMask != 0) | |||||
| setCurrentThreadAffinityMask (affinityMask); | |||||
| run(); | run(); | ||||
| } | } | ||||
| } | } | ||||
| JUCE_CATCH_ALL_ASSERT | JUCE_CATCH_ALL_ASSERT | ||||
| RunningThreadsList::getInstance().remove (this); | |||||
| getCurrentThreadHolder().releaseCurrentThreadStorage(); | |||||
| closeThreadHandle(); | closeThreadHandle(); | ||||
| } | } | ||||
| @@ -145,15 +60,14 @@ void JUCE_API juce_threadEntryPoint (void* userData) | |||||
| static_cast <Thread*> (userData)->threadEntryPoint(); | static_cast <Thread*> (userData)->threadEntryPoint(); | ||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| Thread::Thread (const String& threadName) | |||||
| : threadName_ (threadName), | |||||
| threadHandle_ (nullptr), | |||||
| threadId_ (0), | |||||
| threadPriority_ (5), | |||||
| affinityMask_ (0), | |||||
| threadShouldExit_ (false) | |||||
| Thread::Thread (const String& threadName_) | |||||
| : threadName (threadName_), | |||||
| threadHandle (nullptr), | |||||
| threadId (0), | |||||
| threadPriority (5), | |||||
| affinityMask (0), | |||||
| shouldExit (false) | |||||
| { | { | ||||
| } | } | ||||
| @@ -176,13 +90,13 @@ void Thread::startThread() | |||||
| { | { | ||||
| const ScopedLock sl (startStopLock); | const ScopedLock sl (startStopLock); | ||||
| threadShouldExit_ = false; | |||||
| shouldExit = false; | |||||
| if (threadHandle_ == nullptr) | |||||
| if (threadHandle == nullptr) | |||||
| { | { | ||||
| launchThread(); | launchThread(); | ||||
| setThreadPriority (threadHandle_, threadPriority_); | |||||
| startSuspensionEvent_.signal(); | |||||
| setThreadPriority (threadHandle, threadPriority); | |||||
| startSuspensionEvent.signal(); | |||||
| } | } | ||||
| } | } | ||||
| @@ -190,9 +104,9 @@ void Thread::startThread (const int priority) | |||||
| { | { | ||||
| const ScopedLock sl (startStopLock); | const ScopedLock sl (startStopLock); | ||||
| if (threadHandle_ == nullptr) | |||||
| if (threadHandle == nullptr) | |||||
| { | { | ||||
| threadPriority_ = priority; | |||||
| threadPriority = priority; | |||||
| startThread(); | startThread(); | ||||
| } | } | ||||
| else | else | ||||
| @@ -203,13 +117,13 @@ void Thread::startThread (const int priority) | |||||
| bool Thread::isThreadRunning() const | bool Thread::isThreadRunning() const | ||||
| { | { | ||||
| return threadHandle_ != nullptr; | |||||
| return threadHandle != nullptr; | |||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| void Thread::signalThreadShouldExit() | void Thread::signalThreadShouldExit() | ||||
| { | { | ||||
| threadShouldExit_ = true; | |||||
| shouldExit = true; | |||||
| } | } | ||||
| bool Thread::waitForThreadToExit (const int timeOutMilliseconds) const | bool Thread::waitForThreadToExit (const int timeOutMilliseconds) const | ||||
| @@ -256,62 +170,51 @@ void Thread::stopThread (const int timeOutMilliseconds) | |||||
| killThread(); | killThread(); | ||||
| RunningThreadsList::getInstance().remove (this); | |||||
| threadHandle_ = nullptr; | |||||
| threadId_ = 0; | |||||
| threadHandle = nullptr; | |||||
| threadId = 0; | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| bool Thread::setPriority (const int priority) | |||||
| bool Thread::setPriority (const int newPriority) | |||||
| { | { | ||||
| const ScopedLock sl (startStopLock); | const ScopedLock sl (startStopLock); | ||||
| if (setThreadPriority (threadHandle_, priority)) | |||||
| if (setThreadPriority (threadHandle, newPriority)) | |||||
| { | { | ||||
| threadPriority_ = priority; | |||||
| threadPriority = newPriority; | |||||
| return true; | return true; | ||||
| } | } | ||||
| return false; | return false; | ||||
| } | } | ||||
| bool Thread::setCurrentThreadPriority (const int priority) | |||||
| bool Thread::setCurrentThreadPriority (const int newPriority) | |||||
| { | { | ||||
| return setThreadPriority (0, priority); | |||||
| return setThreadPriority (0, newPriority); | |||||
| } | } | ||||
| void Thread::setAffinityMask (const uint32 affinityMask) | |||||
| void Thread::setAffinityMask (const uint32 newAffinityMask) | |||||
| { | { | ||||
| affinityMask_ = affinityMask; | |||||
| affinityMask = newAffinityMask; | |||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| bool Thread::wait (const int timeOutMilliseconds) const | bool Thread::wait (const int timeOutMilliseconds) const | ||||
| { | { | ||||
| return defaultEvent_.wait (timeOutMilliseconds); | |||||
| return defaultEvent.wait (timeOutMilliseconds); | |||||
| } | } | ||||
| void Thread::notify() const | void Thread::notify() const | ||||
| { | { | ||||
| defaultEvent_.signal(); | |||||
| defaultEvent.signal(); | |||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| int Thread::getNumRunningThreads() | |||||
| { | |||||
| return RunningThreadsList::getInstance().size(); | |||||
| } | |||||
| Thread* Thread::getCurrentThread() | Thread* Thread::getCurrentThread() | ||||
| { | { | ||||
| return RunningThreadsList::getInstance().getThreadWithID (getCurrentThreadId()); | |||||
| } | |||||
| void Thread::stopAllThreads (const int timeOutMilliseconds) | |||||
| { | |||||
| RunningThreadsList::getInstance().stopAll (timeOutMilliseconds); | |||||
| return *getCurrentThreadHolder(); | |||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -142,7 +142,7 @@ public: | |||||
| @see signalThreadShouldExit | @see signalThreadShouldExit | ||||
| */ | */ | ||||
| inline bool threadShouldExit() const { return threadShouldExit_; } | |||||
| inline bool threadShouldExit() const { return shouldExit; } | |||||
| /** Waits for the thread to stop. | /** Waits for the thread to stop. | ||||
| @@ -248,44 +248,30 @@ public: | |||||
| @see getCurrentThreadId | @see getCurrentThreadId | ||||
| */ | */ | ||||
| ThreadID getThreadId() const noexcept { return threadId_; } | |||||
| ThreadID getThreadId() const noexcept { return threadId; } | |||||
| /** Returns the name of the thread. | /** Returns the name of the thread. | ||||
| This is the name that gets set in the constructor. | This is the name that gets set in the constructor. | ||||
| */ | */ | ||||
| const String& getThreadName() const { return threadName_; } | |||||
| const String& getThreadName() const { return threadName; } | |||||
| /** Changes the name of the caller thread. | /** Changes the name of the caller thread. | ||||
| Different OSes may place different length or content limits on this name. | Different OSes may place different length or content limits on this name. | ||||
| */ | */ | ||||
| static void setCurrentThreadName (const String& newThreadName); | static void setCurrentThreadName (const String& newThreadName); | ||||
| //============================================================================== | |||||
| /** Returns the number of currently-running threads. | |||||
| @returns the number of Thread objects known to be currently running. | |||||
| @see stopAllThreads | |||||
| */ | |||||
| static int getNumRunningThreads(); | |||||
| /** Tries to stop all currently-running threads. | |||||
| This will attempt to stop all the threads known to be running at the moment. | |||||
| */ | |||||
| static void stopAllThreads (int timeoutInMillisecs); | |||||
| private: | private: | ||||
| //============================================================================== | //============================================================================== | ||||
| const String threadName_; | |||||
| void* volatile threadHandle_; | |||||
| ThreadID threadId_; | |||||
| const String threadName; | |||||
| void* volatile threadHandle; | |||||
| ThreadID threadId; | |||||
| CriticalSection startStopLock; | CriticalSection startStopLock; | ||||
| WaitableEvent startSuspensionEvent_, defaultEvent_; | |||||
| int threadPriority_; | |||||
| uint32 affinityMask_; | |||||
| bool volatile threadShouldExit_; | |||||
| WaitableEvent startSuspensionEvent, defaultEvent; | |||||
| int threadPriority; | |||||
| uint32 affinityMask; | |||||
| bool volatile shouldExit; | |||||
| #ifndef DOXYGEN | #ifndef DOXYGEN | ||||
| friend void JUCE_API juce_threadEntryPoint (void*); | friend void JUCE_API juce_threadEntryPoint (void*); | ||||
| @@ -295,7 +281,7 @@ private: | |||||
| void closeThreadHandle(); | void closeThreadHandle(); | ||||
| void killThread(); | void killThread(); | ||||
| void threadEntryPoint(); | void threadEntryPoint(); | ||||
| static bool setThreadPriority (void*, int priority); | |||||
| static bool setThreadPriority (void*, int); | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Thread); | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Thread); | ||||
| }; | }; | ||||
| @@ -26,6 +26,9 @@ | |||||
| #ifndef __JUCE_THREADLOCALVALUE_JUCEHEADER__ | #ifndef __JUCE_THREADLOCALVALUE_JUCEHEADER__ | ||||
| #define __JUCE_THREADLOCALVALUE_JUCEHEADER__ | #define __JUCE_THREADLOCALVALUE_JUCEHEADER__ | ||||
| #if ! (JUCE_MSVC || (JUCE_MAC && defined (__clang__))) | |||||
| #define JUCE_NO_COMPILER_THREAD_LOCAL 1 | |||||
| #endif | |||||
| //============================================================================== | //============================================================================== | ||||
| /** | /** | ||||
| @@ -35,14 +38,16 @@ | |||||
| an instance for each thread that requests one. The first time a thread attempts | an instance for each thread that requests one. The first time a thread attempts | ||||
| to access its value, an object is created and added to the list for that thread. | to access its value, an object is created and added to the list for that thread. | ||||
| Typically, you'll probably want to create a static instance of a ThreadLocalValue | |||||
| object, or hold one within a singleton. | |||||
| The templated class for your value could be a primitive type, or any class that | The templated class for your value could be a primitive type, or any class that | ||||
| has a default constructor. | |||||
| has a default constructor and copy operator. | |||||
| Once a thread has accessed its object, that object will not be deleted until the | |||||
| ThreadLocalValue object itself is deleted, even if its thread exits before that. | |||||
| But, because thread ID numbers are used to identify threads, and OSes often re-use | |||||
| these ID numbers, value objects will often be implicitly re-used by new threads whose | |||||
| ID number is the same as one that was used by an earlier thread. | |||||
| When a thread no longer needs to use its value, it can call releaseCurrentThreadStorage() | |||||
| to allow the storage to be re-used by another thread. If a thread exits without calling | |||||
| this method, the object storage will be left allocated until the ThreadLocalValue object | |||||
| is deleted. | |||||
| */ | */ | ||||
| template <typename Type> | template <typename Type> | ||||
| class ThreadLocalValue | class ThreadLocalValue | ||||
| @@ -58,12 +63,14 @@ public: | |||||
| */ | */ | ||||
| ~ThreadLocalValue() | ~ThreadLocalValue() | ||||
| { | { | ||||
| #if JUCE_NO_COMPILER_THREAD_LOCAL | |||||
| for (ObjectHolder* o = first.value; o != nullptr;) | for (ObjectHolder* o = first.value; o != nullptr;) | ||||
| { | { | ||||
| ObjectHolder* const next = o->next; | ObjectHolder* const next = o->next; | ||||
| delete o; | delete o; | ||||
| o = next; | o = next; | ||||
| } | } | ||||
| #endif | |||||
| } | } | ||||
| /** Returns a reference to this thread's instance of the value. | /** Returns a reference to this thread's instance of the value. | ||||
| @@ -71,21 +78,24 @@ public: | |||||
| value object will be created - so if your value's class has a non-trivial | value object will be created - so if your value's class has a non-trivial | ||||
| constructor, be aware that this method could invoke it. | constructor, be aware that this method could invoke it. | ||||
| */ | */ | ||||
| Type& operator*() const noexcept { return get(); } | |||||
| Type& operator*() const noexcept { return get(); } | |||||
| /** Returns a pointer to this thread's instance of the value. | /** Returns a pointer to this thread's instance of the value. | ||||
| Note that the first time a thread tries to access the value, an instance of the | Note that the first time a thread tries to access the value, an instance of the | ||||
| value object will be created - so if your value's class has a non-trivial | value object will be created - so if your value's class has a non-trivial | ||||
| constructor, be aware that this method could invoke it. | constructor, be aware that this method could invoke it. | ||||
| */ | */ | ||||
| operator Type*() const noexcept { return &get(); } | |||||
| operator Type*() const noexcept { return &get(); } | |||||
| /** Accesses a method or field of the value object. | /** Accesses a method or field of the value object. | ||||
| Note that the first time a thread tries to access the value, an instance of the | Note that the first time a thread tries to access the value, an instance of the | ||||
| value object will be created - so if your value's class has a non-trivial | value object will be created - so if your value's class has a non-trivial | ||||
| constructor, be aware that this method could invoke it. | constructor, be aware that this method could invoke it. | ||||
| */ | */ | ||||
| Type* operator->() const noexcept { return &get(); } | |||||
| Type* operator->() const noexcept { return &get(); } | |||||
| /** Assigns a new value to the thread-local object. */ | |||||
| ThreadLocalValue& operator= (const Type& newValue) { get() = newValue; return *this; } | |||||
| /** Returns a reference to this thread's instance of the value. | /** Returns a reference to this thread's instance of the value. | ||||
| Note that the first time a thread tries to access the value, an instance of the | Note that the first time a thread tries to access the value, an instance of the | ||||
| @@ -94,12 +104,31 @@ public: | |||||
| */ | */ | ||||
| Type& get() const noexcept | Type& get() const noexcept | ||||
| { | { | ||||
| #if JUCE_NO_COMPILER_THREAD_LOCAL | |||||
| const Thread::ThreadID threadId = Thread::getCurrentThreadId(); | const Thread::ThreadID threadId = Thread::getCurrentThreadId(); | ||||
| for (ObjectHolder* o = first.get(); o != nullptr; o = o->next) | for (ObjectHolder* o = first.get(); o != nullptr; o = o->next) | ||||
| if (o->threadId == threadId) | if (o->threadId == threadId) | ||||
| return o->object; | return o->object; | ||||
| for (ObjectHolder* o = first.get(); o != nullptr; o = o->next) | |||||
| { | |||||
| if (o->threadId == nullptr) | |||||
| { | |||||
| { | |||||
| SpinLock::ScopedLockType sl (lock); | |||||
| if (o->threadId != nullptr) | |||||
| continue; | |||||
| o->threadId = threadId; | |||||
| } | |||||
| o->object = Type(); | |||||
| return o->object; | |||||
| } | |||||
| } | |||||
| ObjectHolder* const newObject = new ObjectHolder (threadId); | ObjectHolder* const newObject = new ObjectHolder (threadId); | ||||
| do | do | ||||
| @@ -109,17 +138,44 @@ public: | |||||
| while (! first.compareAndSetBool (newObject, newObject->next)); | while (! first.compareAndSetBool (newObject, newObject->next)); | ||||
| return newObject->object; | return newObject->object; | ||||
| #elif JUCE_MAC | |||||
| static __thread Type object; | |||||
| return object; | |||||
| #elif JUCE_MSVC | |||||
| static __declspec(thread) Type object; | |||||
| return object; | |||||
| #endif | |||||
| } | |||||
| /** Called by a thread before it terminates, to allow this class to release | |||||
| any storage associated with the thread. | |||||
| */ | |||||
| void releaseCurrentThreadStorage() | |||||
| { | |||||
| #if JUCE_NO_COMPILER_THREAD_LOCAL | |||||
| const Thread::ThreadID threadId = Thread::getCurrentThreadId(); | |||||
| for (ObjectHolder* o = first.get(); o != nullptr; o = o->next) | |||||
| { | |||||
| if (o->threadId == threadId) | |||||
| { | |||||
| SpinLock::ScopedLockType sl (lock); | |||||
| o->threadId = nullptr; | |||||
| } | |||||
| } | |||||
| #endif | |||||
| } | } | ||||
| private: | private: | ||||
| //============================================================================== | //============================================================================== | ||||
| #if JUCE_NO_COMPILER_THREAD_LOCAL | |||||
| struct ObjectHolder | struct ObjectHolder | ||||
| { | { | ||||
| ObjectHolder (const Thread::ThreadID& threadId_) | ObjectHolder (const Thread::ThreadID& threadId_) | ||||
| : threadId (threadId_), object() | : threadId (threadId_), object() | ||||
| {} | {} | ||||
| const Thread::ThreadID threadId; | |||||
| Thread::ThreadID threadId; | |||||
| ObjectHolder* next; | ObjectHolder* next; | ||||
| Type object; | Type object; | ||||
| @@ -127,6 +183,8 @@ private: | |||||
| }; | }; | ||||
| mutable Atomic<ObjectHolder*> first; | mutable Atomic<ObjectHolder*> first; | ||||
| SpinLock lock; | |||||
| #endif | |||||
| JUCE_DECLARE_NON_COPYABLE (ThreadLocalValue); | JUCE_DECLARE_NON_COPYABLE (ThreadLocalValue); | ||||
| }; | }; | ||||