Browse Source

Improved ThreadLocalValue to use native compiler features where available. Got rid of Thread::stopAllThreads and Thread::getNumRunningThreads (which were just a bad idea, with many safety holes). Replaced the internal RunningThreadsList class with some simpler thread-local storage.

tags/2021-05-28
jules 13 years ago
parent
commit
5f178a962b
5 changed files with 133 additions and 186 deletions
  1. +7
    -7
      modules/juce_core/native/juce_posix_SharedCode.h
  2. +8
    -8
      modules/juce_core/native/juce_win32_Threads.cpp
  3. +39
    -136
      modules/juce_core/threads/juce_Thread.cpp
  4. +11
    -25
      modules/juce_core/threads/juce_Thread.h
  5. +68
    -10
      modules/juce_core/threads/juce_ThreadLocalValue.h

+ 7
- 7
modules/juce_core/native/juce_posix_SharedCode.h View File

@@ -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
} }
} }


+ 8
- 8
modules/juce_core/native/juce_win32_Threads.cpp View File

@@ -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);
} }
} }


+ 39
- 136
modules/juce_core/threads/juce_Thread.cpp View File

@@ -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();
} }
//============================================================================== //==============================================================================


+ 11
- 25
modules/juce_core/threads/juce_Thread.h View File

@@ -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);
}; };


+ 68
- 10
modules/juce_core/threads/juce_ThreadLocalValue.h View File

@@ -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);
}; };


Loading…
Cancel
Save