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()
{
threadHandle_ = 0;
threadHandle = 0;
pthread_t handle = 0;
if (pthread_create (&handle, 0, threadEntryProc, this) == 0)
{
pthread_detach (handle);
threadHandle_ = (void*) handle;
threadId_ = (ThreadID) threadHandle_;
threadHandle = (void*) handle;
threadId = (ThreadID) threadHandle;
}
}
void Thread::closeThreadHandle()
{
threadId_ = 0;
threadHandle_ = 0;
threadId = 0;
threadHandle = 0;
}
void Thread::killThread()
{
if (threadHandle_ != 0)
if (threadHandle != 0)
{
#if JUCE_ANDROID
jassertfalse; // pthread_cancel not available!
#else
pthread_cancel ((pthread_t) threadHandle_);
pthread_cancel ((pthread_t) threadHandle);
#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()
{
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()
{
CloseHandle ((HANDLE) threadHandle_);
threadId_ = 0;
threadHandle_ = 0;
CloseHandle ((HANDLE) threadHandle);
threadId = 0;
threadHandle = 0;
}
void Thread::killThread()
{
if (threadHandle_ != 0)
if (threadHandle != 0)
{
#if JUCE_DEBUG
OutputDebugString (_T("** Warning - Forced thread termination **\n"));
OutputDebugStringA ("** Warning - Forced thread termination **\n");
#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()
{
RunningThreadsList::getInstance().add (this);
getCurrentThreadHolder() = this;
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();
}
}
JUCE_CATCH_ALL_ASSERT
RunningThreadsList::getInstance().remove (this);
getCurrentThreadHolder().releaseCurrentThreadStorage();
closeThreadHandle();
}
@@ -145,15 +60,14 @@ void JUCE_API juce_threadEntryPoint (void* userData)
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);
threadShouldExit_ = false;
shouldExit = false;
if (threadHandle_ == nullptr)
if (threadHandle == nullptr)
{
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);
if (threadHandle_ == nullptr)
if (threadHandle == nullptr)
{
threadPriority_ = priority;
threadPriority = priority;
startThread();
}
else
@@ -203,13 +117,13 @@ void Thread::startThread (const int priority)
bool Thread::isThreadRunning() const
{
return threadHandle_ != nullptr;
return threadHandle != nullptr;
}
//==============================================================================
void Thread::signalThreadShouldExit()
{
threadShouldExit_ = true;
shouldExit = true;
}
bool Thread::waitForThreadToExit (const int timeOutMilliseconds) const
@@ -256,62 +170,51 @@ void Thread::stopThread (const int timeOutMilliseconds)
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);
if (setThreadPriority (threadHandle_, priority))
if (setThreadPriority (threadHandle, newPriority))
{
threadPriority_ = priority;
threadPriority = newPriority;
return true;
}
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
{
return defaultEvent_.wait (timeOutMilliseconds);
return defaultEvent.wait (timeOutMilliseconds);
}
void Thread::notify() const
{
defaultEvent_.signal();
defaultEvent.signal();
}
//==============================================================================
int Thread::getNumRunningThreads()
{
return RunningThreadsList::getInstance().size();
}
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
*/
inline bool threadShouldExit() const { return threadShouldExit_; }
inline bool threadShouldExit() const { return shouldExit; }
/** Waits for the thread to stop.
@@ -248,44 +248,30 @@ public:
@see getCurrentThreadId
*/
ThreadID getThreadId() const noexcept { return threadId_; }
ThreadID getThreadId() const noexcept { return threadId; }
/** Returns the name of the thread.
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.
Different OSes may place different length or content limits on this name.
*/
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:
//==============================================================================
const String threadName_;
void* volatile threadHandle_;
ThreadID threadId_;
const String threadName;
void* volatile threadHandle;
ThreadID threadId;
CriticalSection startStopLock;
WaitableEvent startSuspensionEvent_, defaultEvent_;
int threadPriority_;
uint32 affinityMask_;
bool volatile threadShouldExit_;
WaitableEvent startSuspensionEvent, defaultEvent;
int threadPriority;
uint32 affinityMask;
bool volatile shouldExit;
#ifndef DOXYGEN
friend void JUCE_API juce_threadEntryPoint (void*);
@@ -295,7 +281,7 @@ private:
void closeThreadHandle();
void killThread();
void threadEntryPoint();
static bool setThreadPriority (void*, int priority);
static bool setThreadPriority (void*, int);
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__
#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
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
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>
class ThreadLocalValue
@@ -58,12 +63,14 @@ public:
*/
~ThreadLocalValue()
{
#if JUCE_NO_COMPILER_THREAD_LOCAL
for (ObjectHolder* o = first.value; o != nullptr;)
{
ObjectHolder* const next = o->next;
delete o;
o = next;
}
#endif
}
/** 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
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.
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
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.
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
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.
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
{
#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)
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);
do
@@ -109,17 +138,44 @@ public:
while (! first.compareAndSetBool (newObject, newObject->next));
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:
//==============================================================================
#if JUCE_NO_COMPILER_THREAD_LOCAL
struct ObjectHolder
{
ObjectHolder (const Thread::ThreadID& threadId_)
: threadId (threadId_), object()
{}
const Thread::ThreadID threadId;
Thread::ThreadID threadId;
ObjectHolder* next;
Type object;
@@ -127,6 +183,8 @@ private:
};
mutable Atomic<ObjectHolder*> first;
SpinLock lock;
#endif
JUCE_DECLARE_NON_COPYABLE (ThreadLocalValue);
};


Loading…
Cancel
Save