From 37877037f46e061ba9bdf7f312e8ed7b1d04089e Mon Sep 17 00:00:00 2001 From: Julian Storer Date: Wed, 30 Mar 2011 18:27:26 +0100 Subject: [PATCH] Added new class: SpinLock, and used it to optimise a few bits of code. --- juce_amalgamated.cpp | 131 ++++++++--------- juce_amalgamated.h | 133 +++++++++++++----- .../juce_ResamplingAudioSource.cpp | 7 +- .../juce_ResamplingAudioSource.h | 2 +- src/core/juce_StandardHeader.h | 2 +- src/events/juce_MultiTimer.cpp | 15 +- src/events/juce_MultiTimer.h | 2 +- src/events/juce_Timer.cpp | 24 ++-- src/gui/components/mouse/juce_MouseCursor.cpp | 14 +- .../android/juce_android_NativeCode.cpp | 6 +- src/text/juce_LocalisedStrings.cpp | 10 +- src/threads/juce_CriticalSection.h | 128 +++++++++++++---- src/threads/juce_ReadWriteLock.cpp | 12 +- src/threads/juce_ReadWriteLock.h | 2 +- src/threads/juce_Thread.cpp | 31 +++- src/utilities/juce_DeletedAtShutdown.cpp | 16 +-- src/utilities/juce_DeletedAtShutdown.h | 1 - 17 files changed, 331 insertions(+), 205 deletions(-) diff --git a/juce_amalgamated.cpp b/juce_amalgamated.cpp index bb735f176b..819e6e6d88 100644 --- a/juce_amalgamated.cpp +++ b/juce_amalgamated.cpp @@ -11391,8 +11391,8 @@ const String LocalisedStrings::translate (const String& text) const namespace { - CriticalSection currentMappingsLock; - LocalisedStrings* currentMappings = 0; + SpinLock currentMappingsLock; + ScopedPointer currentMappings; int findCloseQuote (const String& text, int startPos) { @@ -11469,9 +11469,7 @@ void LocalisedStrings::setIgnoresCase (const bool shouldIgnoreCase) void LocalisedStrings::setCurrentMappings (LocalisedStrings* newTranslations) { - const ScopedLock sl (currentMappingsLock); - - delete currentMappings; + const SpinLock::ScopedLockType sl (currentMappingsLock); currentMappings = newTranslations; } @@ -11482,7 +11480,7 @@ LocalisedStrings* LocalisedStrings::getCurrentMappings() const String LocalisedStrings::translateWithCurrentMappings (const String& text) { - const ScopedLock sl (currentMappingsLock); + const SpinLock::ScopedLockType sl (currentMappingsLock); if (currentMappings != 0) return currentMappings->translate (text); @@ -16190,7 +16188,7 @@ ReadWriteLock::~ReadWriteLock() throw() void ReadWriteLock::enterRead() const throw() { const Thread::ThreadID threadId = Thread::getCurrentThreadId(); - const ScopedLock sl (accessLock); + const SpinLock::ScopedLockType sl (accessLock); for (;;) { @@ -16218,7 +16216,7 @@ void ReadWriteLock::enterRead() const throw() return; } - const ScopedUnlock ul (accessLock); + const SpinLock::ScopedUnlockType ul (accessLock); waitEvent.wait (100); } } @@ -16226,7 +16224,7 @@ void ReadWriteLock::enterRead() const throw() void ReadWriteLock::exitRead() const throw() { const Thread::ThreadID threadId = Thread::getCurrentThreadId(); - const ScopedLock sl (accessLock); + const SpinLock::ScopedLockType sl (accessLock); for (int i = 0; i < readerThreads.size(); i += 2) { @@ -16254,7 +16252,7 @@ void ReadWriteLock::exitRead() const throw() void ReadWriteLock::enterWrite() const throw() { const Thread::ThreadID threadId = Thread::getCurrentThreadId(); - const ScopedLock sl (accessLock); + const SpinLock::ScopedLockType sl (accessLock); for (;;) { @@ -16279,7 +16277,7 @@ void ReadWriteLock::enterWrite() const throw() bool ReadWriteLock::tryEnterWrite() const throw() { const Thread::ThreadID threadId = Thread::getCurrentThreadId(); - const ScopedLock sl (accessLock); + const SpinLock::ScopedLockType sl (accessLock); if (readerThreads.size() + numWriters == 0 || threadId == writerThreadId @@ -16296,7 +16294,7 @@ bool ReadWriteLock::tryEnterWrite() const throw() void ReadWriteLock::exitWrite() const throw() { - const ScopedLock sl (accessLock); + const SpinLock::ScopedLockType sl (accessLock); // check this thread actually had the lock.. jassert (numWriters > 0 && writerThreadId == Thread::getCurrentThreadId()); @@ -16324,14 +16322,14 @@ public: void add (Thread* const thread) { - const ScopedLock sl (lock); + const SpinLock::ScopedLockType sl (lock); jassert (! threads.contains (thread)); threads.add (thread); } void remove (Thread* const thread) { - const ScopedLock sl (lock); + const SpinLock::ScopedLockType sl (lock); jassert (threads.contains (thread)); threads.removeValue (thread); } @@ -16343,7 +16341,7 @@ public: Thread* getThreadWithID (const Thread::ThreadID targetID) const throw() { - const ScopedLock sl (lock); + const SpinLock::ScopedLockType sl (lock); for (int i = threads.size(); --i >= 0;) { @@ -16379,11 +16377,11 @@ public: private: Array threads; - CriticalSection lock; + SpinLock lock; void signalAllThreadsToStop() { - const ScopedLock sl (lock); + const SpinLock::ScopedLockType sl (lock); for (int i = threads.size(); --i >= 0;) threads.getUnchecked(i)->signalThreadShouldExit(); @@ -16391,7 +16389,7 @@ private: Thread* getFirstThread() const { - const ScopedLock sl (lock); + const SpinLock::ScopedLockType sl (lock); return threads.getFirst(); } }; @@ -16589,6 +16587,24 @@ void Thread::stopAllThreads (const int timeOutMilliseconds) RunningThreadsList::getInstance().stopAll (timeOutMilliseconds); } +void SpinLock::enter() const throw() +{ + if (! lock.compareAndSetBool (1, 0)) + { + for (int i = 20; --i >= 0;) + if (lock.compareAndSetBool (1, 0)) + return; + + while (! lock.compareAndSetBool (1, 0)) + Thread::yield(); + } +} + +bool SpinLock::tryEnter() const throw() +{ + return lock.compareAndSetBool (1, 0); +} + END_JUCE_NAMESPACE /*** End of inlined file: juce_Thread.cpp ***/ @@ -20212,15 +20228,17 @@ END_JUCE_NAMESPACE /*** Start of inlined file: juce_DeletedAtShutdown.cpp ***/ BEGIN_JUCE_NAMESPACE +static SpinLock deletedAtShutdownLock; + DeletedAtShutdown::DeletedAtShutdown() { - const ScopedLock sl (getLock()); + const SpinLock::ScopedLockType sl (deletedAtShutdownLock); getObjects().add (this); } DeletedAtShutdown::~DeletedAtShutdown() { - const ScopedLock sl (getLock()); + const SpinLock::ScopedLockType sl (deletedAtShutdownLock); getObjects().removeValue (this); } @@ -20231,7 +20249,7 @@ void DeletedAtShutdown::deleteAll() Array localCopy; { - const ScopedLock sl (getLock()); + const SpinLock::ScopedLockType sl (deletedAtShutdownLock); localCopy = getObjects(); } @@ -20243,7 +20261,7 @@ void DeletedAtShutdown::deleteAll() // double-check that it's not already been deleted during another object's destructor. { - const ScopedLock sl (getLock()); + const SpinLock::ScopedLockType sl (deletedAtShutdownLock); if (! getObjects().contains (deletee)) deletee = 0; } @@ -20260,12 +20278,6 @@ void DeletedAtShutdown::deleteAll() getObjects().clear(); // just to make sure the array doesn't have any memory still allocated } -CriticalSection& DeletedAtShutdown::getLock() -{ - static CriticalSection lock; - return lock; -} - Array & DeletedAtShutdown::getObjects() { static Array objects; @@ -24796,14 +24808,14 @@ void ResamplingAudioSource::setResamplingRatio (const double samplesInPerOutputS { jassert (samplesInPerOutputSample > 0); - const ScopedLock sl (ratioLock); + const SpinLock::ScopedLockType sl (ratioLock); ratio = jmax (0.0, samplesInPerOutputSample); } void ResamplingAudioSource::prepareToPlay (int samplesPerBlockExpected, double sampleRate) { - const ScopedLock sl (ratioLock); + const SpinLock::ScopedLockType sl (ratioLock); input->prepareToPlay (samplesPerBlockExpected, sampleRate); @@ -24831,7 +24843,7 @@ void ResamplingAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& inf double localRatio; { - const ScopedLock sl (ratioLock); + const SpinLock::ScopedLockType sl (ratioLock); localRatio = ratio; } @@ -39000,10 +39012,6 @@ public: { } - ~MultiTimerCallback() - { - } - void timerCallback() { owner.timerCallback (timerId); @@ -39025,13 +39033,13 @@ MultiTimer::MultiTimer (const MultiTimer&) throw() MultiTimer::~MultiTimer() { - const ScopedLock sl (timerListLock); + const SpinLock::ScopedLockType sl (timerListLock); timers.clear(); } void MultiTimer::startTimer (const int timerId, const int intervalInMilliseconds) throw() { - const ScopedLock sl (timerListLock); + const SpinLock::ScopedLockType sl (timerListLock); for (int i = timers.size(); --i >= 0;) { @@ -39051,7 +39059,7 @@ void MultiTimer::startTimer (const int timerId, const int intervalInMilliseconds void MultiTimer::stopTimer (const int timerId) throw() { - const ScopedLock sl (timerListLock); + const SpinLock::ScopedLockType sl (timerListLock); for (int i = timers.size(); --i >= 0;) { @@ -39064,7 +39072,7 @@ void MultiTimer::stopTimer (const int timerId) throw() bool MultiTimer::isTimerRunning (const int timerId) const throw() { - const ScopedLock sl (timerListLock); + const SpinLock::ScopedLockType sl (timerListLock); for (int i = timers.size(); --i >= 0;) { @@ -39078,7 +39086,7 @@ bool MultiTimer::isTimerRunning (const int timerId) const throw() int MultiTimer::getTimerInterval (const int timerId) const throw() { - const ScopedLock sl (timerListLock); + const SpinLock::ScopedLockType sl (timerListLock); for (int i = timers.size(); --i >= 0;) { @@ -39180,7 +39188,7 @@ public: void callTimers() { - const ScopedLock sl (lock); + const SpinLock::ScopedLockType sl (lock); while (firstTimer != 0 && firstTimer->countdownMs <= 0) { @@ -39190,7 +39198,7 @@ public: removeTimer (t); addTimer (t); - const ScopedUnlock ul (lock); + const SpinLock::ScopedUnlockType ul (lock); JUCE_TRY { @@ -39237,21 +39245,16 @@ public: if (instance == 0) instance = new InternalTimerThread(); - const ScopedLock sl (instance->lock); instance->addTimer (tim); } static inline void remove (Timer* const tim) throw() { if (instance != 0) - { - const ScopedLock sl (instance->lock); instance->removeTimer (tim); - } } - static inline void resetCounter (Timer* const tim, - const int newCounter) throw() + static inline void resetCounter (Timer* const tim, const int newCounter) throw() { if (instance != 0) { @@ -39261,7 +39264,6 @@ public: if ((tim->next != 0 && tim->next->countdownMs < tim->countdownMs) || (tim->previous != 0 && tim->previous->countdownMs > tim->countdownMs)) { - const ScopedLock sl (instance->lock); instance->removeTimer (tim); instance->addTimer (tim); } @@ -39271,7 +39273,7 @@ public: private: friend class Timer; static InternalTimerThread* instance; - static CriticalSection lock; + static SpinLock lock; Timer* volatile firstTimer; Atomic callbackNeeded; @@ -39362,7 +39364,7 @@ private: int getTimeUntilFirstTimer (const int numMillisecsElapsed) const { - const ScopedLock sl (lock); + const SpinLock::ScopedLockType sl (lock); for (Timer* t = firstTimer; t != 0; t = t->next) t->countdownMs -= numMillisecsElapsed; @@ -39379,7 +39381,7 @@ private: }; InternalTimerThread* InternalTimerThread::instance = 0; -CriticalSection InternalTimerThread::lock; +SpinLock InternalTimerThread::lock; void juce_callAnyTimersSynchronously() { @@ -39397,6 +39399,7 @@ Timer::Timer() throw() next (0) { #if JUCE_DEBUG + const SpinLock::ScopedLockType sl (InternalTimerThread::lock); activeTimers.add (this); #endif } @@ -39408,6 +39411,7 @@ Timer::Timer (const Timer&) throw() next (0) { #if JUCE_DEBUG + const SpinLock::ScopedLockType sl (InternalTimerThread::lock); activeTimers.add (this); #endif } @@ -39423,7 +39427,7 @@ Timer::~Timer() void Timer::startTimer (const int interval) throw() { - const ScopedLock sl (InternalTimerThread::lock); + const SpinLock::ScopedLockType sl (InternalTimerThread::lock); #if JUCE_DEBUG // this isn't a valid object! Your timer might be a dangling pointer or something.. @@ -39444,7 +39448,7 @@ void Timer::startTimer (const int interval) throw() void Timer::stopTimer() throw() { - const ScopedLock sl (InternalTimerThread::lock); + const SpinLock::ScopedLockType sl (InternalTimerThread::lock); #if JUCE_DEBUG // this isn't a valid object! Your timer might be a dangling pointer or something.. @@ -71294,7 +71298,7 @@ public: static SharedCursorHandle* createStandard (const MouseCursor::StandardCursorType type) { - const ScopedLock sl (getLock()); + const SpinLock::ScopedLockType sl (lock); for (int i = 0; i < getCursors().size(); ++i) { @@ -71321,7 +71325,7 @@ public: { if (isStandard) { - const ScopedLock sl (getLock()); + const SpinLock::ScopedLockType sl (lock); getCursors().removeValue (this); } @@ -71337,12 +71341,7 @@ private: Atomic refCount; const MouseCursor::StandardCursorType standardType; const bool isStandard; - - static CriticalSection& getLock() - { - static CriticalSection lock; - return lock; - } + static SpinLock lock; static Array & getCursors() { @@ -71353,6 +71352,8 @@ private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SharedCursorHandle); }; +SpinLock MouseCursor::SharedCursorHandle::lock; + MouseCursor::MouseCursor() : cursorHandle (0) { @@ -284280,7 +284281,7 @@ public: const pthread_t thisThread = pthread_self(); - ScopedLock sl (addRemoveLock); + SpinLock::ScopedLockType sl (addRemoveLock); for (int i = 0; i < maxThreads; ++i) if (threads[i] == thisThread) threads[i] = 0; @@ -284303,11 +284304,11 @@ private: JavaVM* jvm; pthread_t threads [maxThreads]; JNIEnv* envs [maxThreads]; - CriticalSection addRemoveLock; + SpinLock addRemoveLock; void addEnv (JNIEnv* env) { - ScopedLock sl (addRemoveLock); + SpinLock::ScopedLockType sl (addRemoveLock); if (get() == 0) { diff --git a/juce_amalgamated.h b/juce_amalgamated.h index e993884691..9fa8411cc8 100644 --- a/juce_amalgamated.h +++ b/juce_amalgamated.h @@ -73,7 +73,7 @@ namespace JuceDummyNamespace {} */ #define JUCE_MAJOR_VERSION 1 #define JUCE_MINOR_VERSION 53 -#define JUCE_BUILDNUMBER 61 +#define JUCE_BUILDNUMBER 62 /** Current Juce version number. @@ -6460,34 +6460,35 @@ public: #endif /** - Prevents multiple threads from accessing shared objects at the same time. + A mutex class. - @see ScopedLock, Thread, InterProcessLock + A CriticalSection acts as a re-entrant mutex lock. The best way to lock and unlock + one of these is by using RAII in the form of a local ScopedLock object - have a look + through the codebase for many examples of how to do this. + + @see ScopedLock, SpinLock, Thread, InterProcessLock */ class JUCE_API CriticalSection { public: - /** - Creates a CriticalSection object - */ + /** Creates a CriticalSection object. */ CriticalSection() throw(); - /** Destroys a CriticalSection object. - - If the critical section is deleted whilst locked, its subsequent behaviour + /** Destructor. + If the critical section is deleted whilst locked, any subsequent behaviour is unpredictable. */ ~CriticalSection() throw(); - /** Locks this critical section. - - If the lock is currently held by another thread, this will wait until it - becomes free. + /** Acquires the lock. If the lock is already held by the caller thread, the method returns immediately. + If the lock is currently held by another thread, this will wait until it becomes free. + Remember that it's highly recommended that you never use this method, but use a ScopedLock + to manage the locking instead. - @see exit, ScopedLock + @see exit, tryEnter, ScopedLock */ void enter() const throw(); @@ -6521,38 +6522,40 @@ public: private: -#if JUCE_WINDOWS - #if JUCE_64BIT - // To avoid including windows.h in the public Juce includes, we'll just allocate a + #if JUCE_WINDOWS + // To avoid including windows.h in the public JUCE headers, we'll just allocate a // block of memory here that's big enough to be used internally as a windows critical - // section object. - uint8 internal [44]; - #else - uint8 internal [24]; - #endif -#else + // section structure. + #if JUCE_64BIT + uint8 internal [44]; + #else + uint8 internal [24]; + #endif + #else mutable pthread_mutex_t internal; -#endif + #endif JUCE_DECLARE_NON_COPYABLE (CriticalSection); }; /** - A class that can be used in place of a real CriticalSection object. + A class that can be used in place of a real CriticalSection object, but which + doesn't perform any locking. - This is currently used by some templated classes, and should get - optimised out by the compiler. + This is currently used by some templated classes, and most compilers should + manage to optimise it out of existence. - @see Array, OwnedArray, ReferenceCountedArray + @see CriticalSection, Array, OwnedArray, ReferenceCountedArray */ class JUCE_API DummyCriticalSection { public: - inline DummyCriticalSection() throw() {} - inline ~DummyCriticalSection() throw() {} + inline DummyCriticalSection() throw() {} + inline ~DummyCriticalSection() throw() {} - inline void enter() const throw() {} - inline void exit() const throw() {} + inline void enter() const throw() {} + inline bool tryEnter() const throw() { return true; } + inline void exit() const throw() {} /** A dummy scoped-lock type to use with a dummy critical section. */ struct ScopedLockType @@ -6567,6 +6570,65 @@ private: JUCE_DECLARE_NON_COPYABLE (DummyCriticalSection); }; +/** + A simple spin-lock class that can be used as a simple, low-overhead mutex for + uncontended situations. + + Note that unlike a CriticalSection, this type of lock is not re-entrant, and may + be less efficient when used it a highly contended situation, but it's very small and + requires almost no initialisation. + It's most appropriate for simple situations where you're only going to hold the + lock for a very brief time. + + @see CriticalSection +*/ +class JUCE_API SpinLock +{ +public: + inline SpinLock() throw() {} + inline ~SpinLock() throw() {} + + void enter() const throw(); + bool tryEnter() const throw(); + + inline void exit() const throw() + { + jassert (lock.value == 1); // Agh! Releasing a lock that isn't currently held! + lock = 0; + } + + /** A scoped-lock type to use with a SpinLock. */ + class ScopedLockType + { + public: + inline explicit ScopedLockType (const SpinLock& lock_) throw() : lock (lock_) { lock_.enter(); } + inline ~ScopedLockType() throw() { lock.exit(); } + + private: + + const SpinLock& lock; + JUCE_DECLARE_NON_COPYABLE (ScopedLockType); + }; + + /** A scoped-unlocker type to use with a SpinLock. */ + class ScopedUnlockType + { + public: + inline explicit ScopedUnlockType (const SpinLock& lock_) throw() : lock (lock_) { lock_.exit(); } + inline ~ScopedUnlockType() throw() { lock.enter(); } + + private: + + const SpinLock& lock; + JUCE_DECLARE_NON_COPYABLE (ScopedUnlockType); + }; + +private: + + mutable Atomic lock; + JUCE_DECLARE_NON_COPYABLE (SpinLock); +}; + #endif // __JUCE_CRITICALSECTION_JUCEHEADER__ /*** End of inlined file: juce_CriticalSection.h ***/ @@ -22258,7 +22320,7 @@ public: private: - CriticalSection accessLock; + SpinLock accessLock; WaitableEvent waitEvent; mutable int numWaitingWriters, numWriters; mutable Thread::ThreadID writerThreadId; @@ -29894,7 +29956,6 @@ public: static void deleteAll(); private: - static CriticalSection& getLock(); static Array & getObjects(); JUCE_DECLARE_NON_COPYABLE (DeletedAtShutdown); @@ -38026,7 +38087,7 @@ private: int bufferPos, sampsInBuffer; double subSampleOffset; double coefficients[6]; - CriticalSection ratioLock; + SpinLock ratioLock; const int numChannels; HeapBlock destBuffers, srcBuffers; @@ -48804,7 +48865,7 @@ public: private: class MultiTimerCallback; - CriticalSection timerListLock; + SpinLock timerListLock; OwnedArray timers; MultiTimer& operator= (const MultiTimer&); diff --git a/src/audio/audio_sources/juce_ResamplingAudioSource.cpp b/src/audio/audio_sources/juce_ResamplingAudioSource.cpp index ea39441f81..b16b93a95c 100644 --- a/src/audio/audio_sources/juce_ResamplingAudioSource.cpp +++ b/src/audio/audio_sources/juce_ResamplingAudioSource.cpp @@ -28,7 +28,6 @@ BEGIN_JUCE_NAMESPACE #include "juce_ResamplingAudioSource.h" -#include "../../threads/juce_ScopedLock.h" //============================================================================== @@ -56,14 +55,14 @@ void ResamplingAudioSource::setResamplingRatio (const double samplesInPerOutputS { jassert (samplesInPerOutputSample > 0); - const ScopedLock sl (ratioLock); + const SpinLock::ScopedLockType sl (ratioLock); ratio = jmax (0.0, samplesInPerOutputSample); } void ResamplingAudioSource::prepareToPlay (int samplesPerBlockExpected, double sampleRate) { - const ScopedLock sl (ratioLock); + const SpinLock::ScopedLockType sl (ratioLock); input->prepareToPlay (samplesPerBlockExpected, sampleRate); @@ -91,7 +90,7 @@ void ResamplingAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& inf double localRatio; { - const ScopedLock sl (ratioLock); + const SpinLock::ScopedLockType sl (ratioLock); localRatio = ratio; } diff --git a/src/audio/audio_sources/juce_ResamplingAudioSource.h b/src/audio/audio_sources/juce_ResamplingAudioSource.h index 1223440bdd..b6b9902900 100644 --- a/src/audio/audio_sources/juce_ResamplingAudioSource.h +++ b/src/audio/audio_sources/juce_ResamplingAudioSource.h @@ -84,7 +84,7 @@ private: int bufferPos, sampsInBuffer; double subSampleOffset; double coefficients[6]; - CriticalSection ratioLock; + SpinLock ratioLock; const int numChannels; HeapBlock destBuffers, srcBuffers; diff --git a/src/core/juce_StandardHeader.h b/src/core/juce_StandardHeader.h index 5a5d14586c..025eea9b37 100644 --- a/src/core/juce_StandardHeader.h +++ b/src/core/juce_StandardHeader.h @@ -33,7 +33,7 @@ */ #define JUCE_MAJOR_VERSION 1 #define JUCE_MINOR_VERSION 53 -#define JUCE_BUILDNUMBER 61 +#define JUCE_BUILDNUMBER 62 /** Current Juce version number. diff --git a/src/events/juce_MultiTimer.cpp b/src/events/juce_MultiTimer.cpp index 67e0a37f17..a258111dfa 100644 --- a/src/events/juce_MultiTimer.cpp +++ b/src/events/juce_MultiTimer.cpp @@ -29,7 +29,6 @@ BEGIN_JUCE_NAMESPACE #include "juce_MultiTimer.h" #include "juce_Timer.h" -#include "../threads/juce_ScopedLock.h" //============================================================================== @@ -42,10 +41,6 @@ public: { } - ~MultiTimerCallback() - { - } - void timerCallback() { owner.timerCallback (timerId); @@ -68,14 +63,14 @@ MultiTimer::MultiTimer (const MultiTimer&) throw() MultiTimer::~MultiTimer() { - const ScopedLock sl (timerListLock); + const SpinLock::ScopedLockType sl (timerListLock); timers.clear(); } //============================================================================== void MultiTimer::startTimer (const int timerId, const int intervalInMilliseconds) throw() { - const ScopedLock sl (timerListLock); + const SpinLock::ScopedLockType sl (timerListLock); for (int i = timers.size(); --i >= 0;) { @@ -95,7 +90,7 @@ void MultiTimer::startTimer (const int timerId, const int intervalInMilliseconds void MultiTimer::stopTimer (const int timerId) throw() { - const ScopedLock sl (timerListLock); + const SpinLock::ScopedLockType sl (timerListLock); for (int i = timers.size(); --i >= 0;) { @@ -108,7 +103,7 @@ void MultiTimer::stopTimer (const int timerId) throw() bool MultiTimer::isTimerRunning (const int timerId) const throw() { - const ScopedLock sl (timerListLock); + const SpinLock::ScopedLockType sl (timerListLock); for (int i = timers.size(); --i >= 0;) { @@ -122,7 +117,7 @@ bool MultiTimer::isTimerRunning (const int timerId) const throw() int MultiTimer::getTimerInterval (const int timerId) const throw() { - const ScopedLock sl (timerListLock); + const SpinLock::ScopedLockType sl (timerListLock); for (int i = timers.size(); --i >= 0;) { diff --git a/src/events/juce_MultiTimer.h b/src/events/juce_MultiTimer.h index 8bb9b01bee..f3f6db024f 100644 --- a/src/events/juce_MultiTimer.h +++ b/src/events/juce_MultiTimer.h @@ -123,7 +123,7 @@ public: //============================================================================== private: class MultiTimerCallback; - CriticalSection timerListLock; + SpinLock timerListLock; OwnedArray timers; MultiTimer& operator= (const MultiTimer&); diff --git a/src/events/juce_Timer.cpp b/src/events/juce_Timer.cpp index 2602a35279..b38e2557a9 100644 --- a/src/events/juce_Timer.cpp +++ b/src/events/juce_Timer.cpp @@ -121,7 +121,7 @@ public: void callTimers() { - const ScopedLock sl (lock); + const SpinLock::ScopedLockType sl (lock); while (firstTimer != 0 && firstTimer->countdownMs <= 0) { @@ -131,7 +131,7 @@ public: removeTimer (t); addTimer (t); - const ScopedUnlock ul (lock); + const SpinLock::ScopedUnlockType ul (lock); JUCE_TRY { @@ -178,21 +178,16 @@ public: if (instance == 0) instance = new InternalTimerThread(); - const ScopedLock sl (instance->lock); instance->addTimer (tim); } static inline void remove (Timer* const tim) throw() { if (instance != 0) - { - const ScopedLock sl (instance->lock); instance->removeTimer (tim); - } } - static inline void resetCounter (Timer* const tim, - const int newCounter) throw() + static inline void resetCounter (Timer* const tim, const int newCounter) throw() { if (instance != 0) { @@ -202,7 +197,6 @@ public: if ((tim->next != 0 && tim->next->countdownMs < tim->countdownMs) || (tim->previous != 0 && tim->previous->countdownMs > tim->countdownMs)) { - const ScopedLock sl (instance->lock); instance->removeTimer (tim); instance->addTimer (tim); } @@ -212,7 +206,7 @@ public: private: friend class Timer; static InternalTimerThread* instance; - static CriticalSection lock; + static SpinLock lock; Timer* volatile firstTimer; Atomic callbackNeeded; @@ -304,7 +298,7 @@ private: int getTimeUntilFirstTimer (const int numMillisecsElapsed) const { - const ScopedLock sl (lock); + const SpinLock::ScopedLockType sl (lock); for (Timer* t = firstTimer; t != 0; t = t->next) t->countdownMs -= numMillisecsElapsed; @@ -321,7 +315,7 @@ private: }; InternalTimerThread* InternalTimerThread::instance = 0; -CriticalSection InternalTimerThread::lock; +SpinLock InternalTimerThread::lock; void juce_callAnyTimersSynchronously() { @@ -340,6 +334,7 @@ Timer::Timer() throw() next (0) { #if JUCE_DEBUG + const SpinLock::ScopedLockType sl (InternalTimerThread::lock); activeTimers.add (this); #endif } @@ -351,6 +346,7 @@ Timer::Timer (const Timer&) throw() next (0) { #if JUCE_DEBUG + const SpinLock::ScopedLockType sl (InternalTimerThread::lock); activeTimers.add (this); #endif } @@ -366,7 +362,7 @@ Timer::~Timer() void Timer::startTimer (const int interval) throw() { - const ScopedLock sl (InternalTimerThread::lock); + const SpinLock::ScopedLockType sl (InternalTimerThread::lock); #if JUCE_DEBUG // this isn't a valid object! Your timer might be a dangling pointer or something.. @@ -387,7 +383,7 @@ void Timer::startTimer (const int interval) throw() void Timer::stopTimer() throw() { - const ScopedLock sl (InternalTimerThread::lock); + const SpinLock::ScopedLockType sl (InternalTimerThread::lock); #if JUCE_DEBUG // this isn't a valid object! Your timer might be a dangling pointer or something.. diff --git a/src/gui/components/mouse/juce_MouseCursor.cpp b/src/gui/components/mouse/juce_MouseCursor.cpp index 801eebf37a..9b0e279269 100644 --- a/src/gui/components/mouse/juce_MouseCursor.cpp +++ b/src/gui/components/mouse/juce_MouseCursor.cpp @@ -31,7 +31,6 @@ BEGIN_JUCE_NAMESPACE #include "../juce_Component.h" #include "../lookandfeel/juce_LookAndFeel.h" #include "../mouse/juce_MouseInputSource.h" -#include "../../../threads/juce_ScopedLock.h" //============================================================================== @@ -61,7 +60,7 @@ public: static SharedCursorHandle* createStandard (const MouseCursor::StandardCursorType type) { - const ScopedLock sl (getLock()); + const SpinLock::ScopedLockType sl (lock); for (int i = 0; i < getCursors().size(); ++i) { @@ -88,7 +87,7 @@ public: { if (isStandard) { - const ScopedLock sl (getLock()); + const SpinLock::ScopedLockType sl (lock); getCursors().removeValue (this); } @@ -105,12 +104,7 @@ private: Atomic refCount; const MouseCursor::StandardCursorType standardType; const bool isStandard; - - static CriticalSection& getLock() - { - static CriticalSection lock; - return lock; - } + static SpinLock lock; static Array & getCursors() { @@ -121,6 +115,8 @@ private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SharedCursorHandle); }; +SpinLock MouseCursor::SharedCursorHandle::lock; + //============================================================================== MouseCursor::MouseCursor() : cursorHandle (0) diff --git a/src/native/android/juce_android_NativeCode.cpp b/src/native/android/juce_android_NativeCode.cpp index f25376f9c0..1953e90dca 100644 --- a/src/native/android/juce_android_NativeCode.cpp +++ b/src/native/android/juce_android_NativeCode.cpp @@ -312,7 +312,7 @@ public: const pthread_t thisThread = pthread_self(); - ScopedLock sl (addRemoveLock); + SpinLock::ScopedLockType sl (addRemoveLock); for (int i = 0; i < maxThreads; ++i) if (threads[i] == thisThread) threads[i] = 0; @@ -335,11 +335,11 @@ private: JavaVM* jvm; pthread_t threads [maxThreads]; JNIEnv* envs [maxThreads]; - CriticalSection addRemoveLock; + SpinLock addRemoveLock; void addEnv (JNIEnv* env) { - ScopedLock sl (addRemoveLock); + SpinLock::ScopedLockType sl (addRemoveLock); if (get() == 0) { diff --git a/src/text/juce_LocalisedStrings.cpp b/src/text/juce_LocalisedStrings.cpp index fa297447d9..4529a9f84e 100644 --- a/src/text/juce_LocalisedStrings.cpp +++ b/src/text/juce_LocalisedStrings.cpp @@ -53,8 +53,8 @@ const String LocalisedStrings::translate (const String& text) const namespace { - CriticalSection currentMappingsLock; - LocalisedStrings* currentMappings = 0; + SpinLock currentMappingsLock; + ScopedPointer currentMappings; int findCloseQuote (const String& text, int startPos) { @@ -132,9 +132,7 @@ void LocalisedStrings::setIgnoresCase (const bool shouldIgnoreCase) //============================================================================== void LocalisedStrings::setCurrentMappings (LocalisedStrings* newTranslations) { - const ScopedLock sl (currentMappingsLock); - - delete currentMappings; + const SpinLock::ScopedLockType sl (currentMappingsLock); currentMappings = newTranslations; } @@ -145,7 +143,7 @@ LocalisedStrings* LocalisedStrings::getCurrentMappings() const String LocalisedStrings::translateWithCurrentMappings (const String& text) { - const ScopedLock sl (currentMappingsLock); + const SpinLock::ScopedLockType sl (currentMappingsLock); if (currentMappings != 0) return currentMappings->translate (text); diff --git a/src/threads/juce_CriticalSection.h b/src/threads/juce_CriticalSection.h index 55bfed8792..0291933cc0 100644 --- a/src/threads/juce_CriticalSection.h +++ b/src/threads/juce_CriticalSection.h @@ -34,35 +34,36 @@ //============================================================================== /** - Prevents multiple threads from accessing shared objects at the same time. + A mutex class. - @see ScopedLock, Thread, InterProcessLock + A CriticalSection acts as a re-entrant mutex lock. The best way to lock and unlock + one of these is by using RAII in the form of a local ScopedLock object - have a look + through the codebase for many examples of how to do this. + + @see ScopedLock, SpinLock, Thread, InterProcessLock */ class JUCE_API CriticalSection { public: //============================================================================== - /** - Creates a CriticalSection object - */ + /** Creates a CriticalSection object. */ CriticalSection() throw(); - /** Destroys a CriticalSection object. - - If the critical section is deleted whilst locked, its subsequent behaviour + /** Destructor. + If the critical section is deleted whilst locked, any subsequent behaviour is unpredictable. */ ~CriticalSection() throw(); //============================================================================== - /** Locks this critical section. - - If the lock is currently held by another thread, this will wait until it - becomes free. + /** Acquires the lock. If the lock is already held by the caller thread, the method returns immediately. + If the lock is currently held by another thread, this will wait until it becomes free. + Remember that it's highly recommended that you never use this method, but use a ScopedLock + to manage the locking instead. - @see exit, ScopedLock + @see exit, tryEnter, ScopedLock */ void enter() const throw(); @@ -99,18 +100,18 @@ public: private: //============================================================================== -#if JUCE_WINDOWS - #if JUCE_64BIT - // To avoid including windows.h in the public Juce includes, we'll just allocate a + #if JUCE_WINDOWS + // To avoid including windows.h in the public JUCE headers, we'll just allocate a // block of memory here that's big enough to be used internally as a windows critical - // section object. - uint8 internal [44]; - #else - uint8 internal [24]; - #endif -#else + // section structure. + #if JUCE_64BIT + uint8 internal [44]; + #else + uint8 internal [24]; + #endif + #else mutable pthread_mutex_t internal; -#endif + #endif JUCE_DECLARE_NON_COPYABLE (CriticalSection); }; @@ -118,21 +119,23 @@ private: //============================================================================== /** - A class that can be used in place of a real CriticalSection object. + A class that can be used in place of a real CriticalSection object, but which + doesn't perform any locking. - This is currently used by some templated classes, and should get - optimised out by the compiler. + This is currently used by some templated classes, and most compilers should + manage to optimise it out of existence. - @see Array, OwnedArray, ReferenceCountedArray + @see CriticalSection, Array, OwnedArray, ReferenceCountedArray */ class JUCE_API DummyCriticalSection { public: - inline DummyCriticalSection() throw() {} - inline ~DummyCriticalSection() throw() {} + inline DummyCriticalSection() throw() {} + inline ~DummyCriticalSection() throw() {} - inline void enter() const throw() {} - inline void exit() const throw() {} + inline void enter() const throw() {} + inline bool tryEnter() const throw() { return true; } + inline void exit() const throw() {} //============================================================================== /** A dummy scoped-lock type to use with a dummy critical section. */ @@ -149,4 +152,67 @@ private: }; +//============================================================================== +/** + A simple spin-lock class that can be used as a simple, low-overhead mutex for + uncontended situations. + + Note that unlike a CriticalSection, this type of lock is not re-entrant, and may + be less efficient when used it a highly contended situation, but it's very small and + requires almost no initialisation. + It's most appropriate for simple situations where you're only going to hold the + lock for a very brief time. + + @see CriticalSection +*/ +class JUCE_API SpinLock +{ +public: + inline SpinLock() throw() {} + inline ~SpinLock() throw() {} + + void enter() const throw(); + bool tryEnter() const throw(); + + inline void exit() const throw() + { + jassert (lock.value == 1); // Agh! Releasing a lock that isn't currently held! + lock = 0; + } + + //============================================================================== + /** A scoped-lock type to use with a SpinLock. */ + class ScopedLockType + { + public: + inline explicit ScopedLockType (const SpinLock& lock_) throw() : lock (lock_) { lock_.enter(); } + inline ~ScopedLockType() throw() { lock.exit(); } + + private: + //============================================================================== + const SpinLock& lock; + JUCE_DECLARE_NON_COPYABLE (ScopedLockType); + }; + + //============================================================================== + /** A scoped-unlocker type to use with a SpinLock. */ + class ScopedUnlockType + { + public: + inline explicit ScopedUnlockType (const SpinLock& lock_) throw() : lock (lock_) { lock_.exit(); } + inline ~ScopedUnlockType() throw() { lock.enter(); } + + private: + //============================================================================== + const SpinLock& lock; + JUCE_DECLARE_NON_COPYABLE (ScopedUnlockType); + }; + +private: + //============================================================================== + mutable Atomic lock; + JUCE_DECLARE_NON_COPYABLE (SpinLock); +}; + + #endif // __JUCE_CRITICALSECTION_JUCEHEADER__ diff --git a/src/threads/juce_ReadWriteLock.cpp b/src/threads/juce_ReadWriteLock.cpp index 06a2da6aee..91450b5939 100644 --- a/src/threads/juce_ReadWriteLock.cpp +++ b/src/threads/juce_ReadWriteLock.cpp @@ -50,7 +50,7 @@ ReadWriteLock::~ReadWriteLock() throw() void ReadWriteLock::enterRead() const throw() { const Thread::ThreadID threadId = Thread::getCurrentThreadId(); - const ScopedLock sl (accessLock); + const SpinLock::ScopedLockType sl (accessLock); for (;;) { @@ -78,7 +78,7 @@ void ReadWriteLock::enterRead() const throw() return; } - const ScopedUnlock ul (accessLock); + const SpinLock::ScopedUnlockType ul (accessLock); waitEvent.wait (100); } } @@ -86,7 +86,7 @@ void ReadWriteLock::enterRead() const throw() void ReadWriteLock::exitRead() const throw() { const Thread::ThreadID threadId = Thread::getCurrentThreadId(); - const ScopedLock sl (accessLock); + const SpinLock::ScopedLockType sl (accessLock); for (int i = 0; i < readerThreads.size(); i += 2) { @@ -115,7 +115,7 @@ void ReadWriteLock::exitRead() const throw() void ReadWriteLock::enterWrite() const throw() { const Thread::ThreadID threadId = Thread::getCurrentThreadId(); - const ScopedLock sl (accessLock); + const SpinLock::ScopedLockType sl (accessLock); for (;;) { @@ -140,7 +140,7 @@ void ReadWriteLock::enterWrite() const throw() bool ReadWriteLock::tryEnterWrite() const throw() { const Thread::ThreadID threadId = Thread::getCurrentThreadId(); - const ScopedLock sl (accessLock); + const SpinLock::ScopedLockType sl (accessLock); if (readerThreads.size() + numWriters == 0 || threadId == writerThreadId @@ -157,7 +157,7 @@ bool ReadWriteLock::tryEnterWrite() const throw() void ReadWriteLock::exitWrite() const throw() { - const ScopedLock sl (accessLock); + const SpinLock::ScopedLockType sl (accessLock); // check this thread actually had the lock.. jassert (numWriters > 0 && writerThreadId == Thread::getCurrentThreadId()); diff --git a/src/threads/juce_ReadWriteLock.h b/src/threads/juce_ReadWriteLock.h index 5fff1f7cf8..c4badbac30 100644 --- a/src/threads/juce_ReadWriteLock.h +++ b/src/threads/juce_ReadWriteLock.h @@ -124,7 +124,7 @@ public: private: //============================================================================== - CriticalSection accessLock; + SpinLock accessLock; WaitableEvent waitEvent; mutable int numWaitingWriters, numWriters; mutable Thread::ThreadID writerThreadId; diff --git a/src/threads/juce_Thread.cpp b/src/threads/juce_Thread.cpp index dbeab6f918..0e517688f5 100644 --- a/src/threads/juce_Thread.cpp +++ b/src/threads/juce_Thread.cpp @@ -42,14 +42,14 @@ public: void add (Thread* const thread) { - const ScopedLock sl (lock); + const SpinLock::ScopedLockType sl (lock); jassert (! threads.contains (thread)); threads.add (thread); } void remove (Thread* const thread) { - const ScopedLock sl (lock); + const SpinLock::ScopedLockType sl (lock); jassert (threads.contains (thread)); threads.removeValue (thread); } @@ -61,7 +61,7 @@ public: Thread* getThreadWithID (const Thread::ThreadID targetID) const throw() { - const ScopedLock sl (lock); + const SpinLock::ScopedLockType sl (lock); for (int i = threads.size(); --i >= 0;) { @@ -97,11 +97,11 @@ public: private: Array threads; - CriticalSection lock; + SpinLock lock; void signalAllThreadsToStop() { - const ScopedLock sl (lock); + const SpinLock::ScopedLockType sl (lock); for (int i = threads.size(); --i >= 0;) threads.getUnchecked(i)->signalThreadShouldExit(); @@ -109,7 +109,7 @@ private: Thread* getFirstThread() const { - const ScopedLock sl (lock); + const SpinLock::ScopedLockType sl (lock); return threads.getFirst(); } }; @@ -316,5 +316,24 @@ void Thread::stopAllThreads (const int timeOutMilliseconds) RunningThreadsList::getInstance().stopAll (timeOutMilliseconds); } +//============================================================================== +void SpinLock::enter() const throw() +{ + if (! lock.compareAndSetBool (1, 0)) + { + for (int i = 20; --i >= 0;) + if (lock.compareAndSetBool (1, 0)) + return; + + while (! lock.compareAndSetBool (1, 0)) + Thread::yield(); + } +} + +bool SpinLock::tryEnter() const throw() +{ + return lock.compareAndSetBool (1, 0); +} + END_JUCE_NAMESPACE diff --git a/src/utilities/juce_DeletedAtShutdown.cpp b/src/utilities/juce_DeletedAtShutdown.cpp index d09a9f666a..96dadb1139 100644 --- a/src/utilities/juce_DeletedAtShutdown.cpp +++ b/src/utilities/juce_DeletedAtShutdown.cpp @@ -34,15 +34,17 @@ BEGIN_JUCE_NAMESPACE //============================================================================== +static SpinLock deletedAtShutdownLock; + DeletedAtShutdown::DeletedAtShutdown() { - const ScopedLock sl (getLock()); + const SpinLock::ScopedLockType sl (deletedAtShutdownLock); getObjects().add (this); } DeletedAtShutdown::~DeletedAtShutdown() { - const ScopedLock sl (getLock()); + const SpinLock::ScopedLockType sl (deletedAtShutdownLock); getObjects().removeValue (this); } @@ -53,7 +55,7 @@ void DeletedAtShutdown::deleteAll() Array localCopy; { - const ScopedLock sl (getLock()); + const SpinLock::ScopedLockType sl (deletedAtShutdownLock); localCopy = getObjects(); } @@ -65,7 +67,7 @@ void DeletedAtShutdown::deleteAll() // double-check that it's not already been deleted during another object's destructor. { - const ScopedLock sl (getLock()); + const SpinLock::ScopedLockType sl (deletedAtShutdownLock); if (! getObjects().contains (deletee)) deletee = 0; } @@ -82,12 +84,6 @@ void DeletedAtShutdown::deleteAll() getObjects().clear(); // just to make sure the array doesn't have any memory still allocated } -CriticalSection& DeletedAtShutdown::getLock() -{ - static CriticalSection lock; - return lock; -} - Array & DeletedAtShutdown::getObjects() { static Array objects; diff --git a/src/utilities/juce_DeletedAtShutdown.h b/src/utilities/juce_DeletedAtShutdown.h index fc24aa5988..1773c908a9 100644 --- a/src/utilities/juce_DeletedAtShutdown.h +++ b/src/utilities/juce_DeletedAtShutdown.h @@ -63,7 +63,6 @@ public: static void deleteAll(); private: - static CriticalSection& getLock(); static Array & getObjects(); JUCE_DECLARE_NON_COPYABLE (DeletedAtShutdown);