diff --git a/modules/juce_core/threads/juce_ReadWriteLock.h b/modules/juce_core/threads/juce_ReadWriteLock.h index 14705b2ee7..a26d6df2a7 100644 --- a/modules/juce_core/threads/juce_ReadWriteLock.h +++ b/modules/juce_core/threads/juce_ReadWriteLock.h @@ -74,7 +74,7 @@ public: thread has it locked for writing, then this will fail and return false. @returns true if the lock is successfully gained. - @see exitRead, ScopedReadLock + @see exitRead, ScopedTryReadLock */ bool tryEnterRead() const noexcept; @@ -106,7 +106,7 @@ public: to obtain the lock. @returns true if the lock is successfully gained. - @see enterWrite + @see enterWrite, ScopedTryWriteLock */ bool tryEnterWrite() const noexcept; diff --git a/modules/juce_core/threads/juce_ScopedReadLock.h b/modules/juce_core/threads/juce_ScopedReadLock.h index 97c2fd1c42..b2d7e18ce0 100644 --- a/modules/juce_core/threads/juce_ScopedReadLock.h +++ b/modules/juce_core/threads/juce_ScopedReadLock.h @@ -81,4 +81,90 @@ private: JUCE_DECLARE_NON_COPYABLE (ScopedReadLock) }; +//============================================================================== +/** + Automatically locks and unlocks a ReadWriteLock object. + + Use one of these as a local variable to control access to a ReadWriteLock. + + e.g. @code + + ReadWriteLock myLock; + + for (;;) + { + const ScopedTryReadLock myScopedTryLock (myLock); + + // Unlike using a ScopedReadLock, this may fail to actually get the lock, so you + // should test this with the isLocked() method before doing your thread-unsafe + // action. + + if (myScopedTryLock.isLocked()) + { + ...do some stuff... + } + else + { + ..our attempt at locking failed because a write lock has already been issued.. + } + + // myLock gets unlocked here (if it was locked). + } + @endcode + + @see ReadWriteLock, ScopedTryWriteLock + + @tags{Core} +*/ +class JUCE_API ScopedTryReadLock +{ +public: + //============================================================================== + /** Creates a ScopedTryReadLock and calls ReadWriteLock::tryEnterRead() as soon as it is + created. When the ScopedTryReadLock object is destructed, the ReadWriteLock will be unlocked + (if it was successfully acquired). + + Make sure this object is created and destructed by the same thread, otherwise there are no + guarantees what will happen! Best just to use it as a local stack object, rather than creating + one with the new() operator. + */ + explicit ScopedTryReadLock (ReadWriteLock& lockIn) + : ScopedTryReadLock (lockIn, true) {} + + /** Creates a ScopedTryReadLock. + + If acquireLockOnInitialisation is true then as soon as it is created, this will call + ReadWriteLock::tryEnterRead(), and when the ScopedTryReadLock object is destructed, the + ReadWriteLock will be unlocked (if it was successfully acquired). + + Make sure this object is created and destructed by the same thread, otherwise there are no + guarantees what will happen! Best just to use it as a local stack object, rather than creating + one with the new() operator. + */ + ScopedTryReadLock (ReadWriteLock& lockIn, bool acquireLockOnInitialisation) noexcept + : lock (lockIn), lockWasSuccessful (acquireLockOnInitialisation && lock.tryEnterRead()) {} + + /** Destructor. + + The ReadWriteLock's exitRead() method will be called when the destructor is called. + + Make sure this object is created and destructed by the same thread, otherwise there are no + guarantees what will happen! + */ + ~ScopedTryReadLock() noexcept { if (lockWasSuccessful) lock.exitRead(); } + + /** Returns true if the mutex was successfully locked. */ + bool isLocked() const noexcept { return lockWasSuccessful; } + + /** Retry gaining the lock by calling tryEnter on the underlying lock. */ + bool retryLock() noexcept { return lockWasSuccessful = lock.tryEnterRead(); } + +private: + //============================================================================== + ReadWriteLock& lock; + bool lockWasSuccessful; + + JUCE_DECLARE_NON_COPYABLE (ScopedTryReadLock) +}; + } // namespace juce diff --git a/modules/juce_core/threads/juce_ScopedWriteLock.h b/modules/juce_core/threads/juce_ScopedWriteLock.h index b3eb5e3559..5f7290e70c 100644 --- a/modules/juce_core/threads/juce_ScopedWriteLock.h +++ b/modules/juce_core/threads/juce_ScopedWriteLock.h @@ -81,4 +81,90 @@ private: JUCE_DECLARE_NON_COPYABLE (ScopedWriteLock) }; +//============================================================================== +/** + Automatically locks and unlocks a ReadWriteLock object. + + Use one of these as a local variable to control access to a ReadWriteLock. + + e.g. @code + + ReadWriteLock myLock; + + for (;;) + { + const ScopedTryWriteLock myScopedTryLock (myLock); + + // Unlike using a ScopedWriteLock, this may fail to actually get the lock, so you + // should test this with the isLocked() method before doing your thread-unsafe + // action. + + if (myScopedTryLock.isLocked()) + { + ...do some stuff... + } + else + { + ..our attempt at locking failed because some other thread has already locked the object.. + } + + // myLock gets unlocked here (if it was locked). + } + @endcode + + @see ReadWriteLock, ScopedTryWriteLock + + @tags{Core} +*/ +class JUCE_API ScopedTryWriteLock +{ +public: + //============================================================================== + /** Creates a ScopedTryWriteLock and calls ReadWriteLock::tryEnterWrite() immediately. + When the ScopedTryWriteLock object is destructed, the ReadWriteLock will be unlocked + (if it was successfully acquired). + + Make sure this object is created and destructed by the same thread, otherwise there are no + guarantees what will happen! Best just to use it as a local stack object, rather than creating + one with the new() operator. + */ + ScopedTryWriteLock (ReadWriteLock& lockIn) noexcept + : ScopedTryWriteLock (lockIn, true) {} + + /** Creates a ScopedTryWriteLock. + + If acquireLockOnInitialisation is true then as soon as it is created, this will call + ReadWriteLock::tryEnterWrite(), and when the ScopedTryWriteLock object is destructed, the + ReadWriteLock will be unlocked (if it was successfully acquired). + + Make sure this object is created and destructed by the same thread, otherwise there are no + guarantees what will happen! Best just to use it as a local stack object, rather than creating + one with the new() operator. + */ + ScopedTryWriteLock (ReadWriteLock& lockIn, bool acquireLockOnInitialisation) noexcept + : lock (lockIn), lockWasSuccessful (acquireLockOnInitialisation && lock.tryEnterWrite()) {} + + /** Destructor. + + The ReadWriteLock's exitWrite() method will be called when the destructor is called. + + Make sure this object is created and destructed by the same thread, + otherwise there are no guarantees what will happen! + */ + ~ScopedTryWriteLock() noexcept { if (lockWasSuccessful) lock.exitWrite(); } + + /** Returns true if the mutex was successfully locked. */ + bool isLocked() const noexcept { return lockWasSuccessful; } + + /** Retry gaining the lock by calling tryEnter on the underlying lock. */ + bool retryLock() noexcept { return lockWasSuccessful = lock.tryEnterWrite(); } + +private: + //============================================================================== + ReadWriteLock& lock; + bool lockWasSuccessful; + + JUCE_DECLARE_NON_COPYABLE (ScopedTryWriteLock) +}; + }