Skip to content

Commit

Permalink
Merge pull request #142 from oblivioncth/feature/tss
Browse files Browse the repository at this point in the history
Add ThreadSafeSingleton. Easy singleton pattern with thread-safe guard.
  • Loading branch information
oblivioncth authored Nov 10, 2024
2 parents 2943c1f + 3c3edf6 commit 6fc9c4e
Show file tree
Hide file tree
Showing 6 changed files with 154 additions and 17 deletions.
2 changes: 2 additions & 0 deletions lib/core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ qx_add_component("Core"
qx-processbider.h
qx-progressgroup.h
qx-table.h
qx-threadsafesingleton.h
qx-versionnumber.h
qx-regularexpression.h
qx-setonce.h
Expand Down Expand Up @@ -87,6 +88,7 @@ qx_add_component("Core"
qx-array.dox
qx-setonce.dox
qx-table.dox
qx-threadsafesingleton.dox
LINKS
PUBLIC
${Qt}::Core
Expand Down
33 changes: 33 additions & 0 deletions lib/core/doc/res/snippets/qx-threadsafesingleton.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//! [0]
class MySingleton : Qx::ThreadSafeSingleton<MySingleton>
{
QX_THREAD_SAFE_SINGLETON(MySingleton);
private:
std::string mData;
MySingleton() = default; // Generally should be private

public:
doStuffSafely() { mData = "I'm for sure set while not being read!"; }
checkStuffSafely() { return mData; // Not being written to when returned }
}

//...

void functionInArbitraryThread()
{
auto singleton = MySingleton::instance();
// This function now has a exclusive access to MySingleton (i.e. a mutex lock is established)
singleton->doStuffSafely();

// Unlocked when 'singleton' goes out of scope, or is manually unlocked.
}

void functionInAnotherThread()
{
// Safely lock and read. It's guarenteed that no other thread is using MySingleton
// after the instance is obtained.
auto singleton = MySingleton::instance();
std::string info = singleton->checkStuffSafely();
//...
}
//! [0]
2 changes: 1 addition & 1 deletion lib/core/include/qx/core/qx-exclusiveaccess.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class QRecursiveMutex;
namespace Qx
{

template<typename AccessType, typename Mutex>
template<typename AccessType, typename Mutex = QMutex>
requires any_of<Mutex, QMutex, QRecursiveMutex>
class ExclusiveAccess
{
Expand Down
44 changes: 44 additions & 0 deletions lib/core/include/qx/core/qx-threadsafesingleton.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#ifndef QX_THREADSAFE_SINGLETON
#define QX_THREADSAFE_SINGLETON

// Intra-component Includes
#include "qx/core/qx-exclusiveaccess.h"

// Extra-component Includes
#include "qx/utility/qx-concepts.h"

class QMutex;
class QRecursiveMutex;

namespace Qx
{

template<class Singleton, typename Mutex = QMutex>
requires any_of<Mutex, QMutex, QRecursiveMutex>
class ThreadSafeSingleton
{
//-Class Members---------------------------------------------------------------------------------------------
private:
// Needs to be static so it can be locked before the the singleton is created, or else a race in instance() could occur.
static inline constinit Mutex smMutex;

//-Constructor----------------------------------------------------------------------------------------------
protected:
ThreadSafeSingleton() = default;

//-Class Functions----------------------------------------------------------------------------------------------
public:
static Qx::ExclusiveAccess<Singleton, QMutex> instance()
{
static Singleton s;
return Qx::ExclusiveAccess(&s, &smMutex); // Provides locked access to singleton, that unlocks when destroyed
}
};

//-Macros----------------------------------------------------------------------------------------------------------
// Macro to be used in all derivatives
#define QX_THREAD_SAFE_SINGLETON(Singleton) friend ThreadSafeSingleton<Singleton>

}

#endif // QX_THREADSAFE_SINGLETON
32 changes: 16 additions & 16 deletions lib/core/src/qx-exclusiveaccess.dox
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ namespace Qx
//-Constructor----------------------------------------------------------------------------------------------
//Public:
/*!
* @fn ExclusiveAccess<Type, Mutex>::ExclusiveAccess(AccessType* data, Mutex* mutex)
* @fn ExclusiveAccess<AccessType, Mutex>::ExclusiveAccess(AccessType* data, Mutex* mutex)
*
* Constructs an ExclusiveAccess and locks @a mutex. The mutex will be unlocked when the ExclusiveAccess is
* destroyed. If @a mutex is @c nullptr, ExclusiveAccess only provides access to @a data.
Expand All @@ -32,7 +32,7 @@ namespace Qx
*/

/*!
* @fn ExclusiveAccess<Type, Mutex>::ExclusiveAccess(ExclusiveAccess&& other)
* @fn ExclusiveAccess<AccessType, Mutex>::ExclusiveAccess(ExclusiveAccess&& other)
*
* Move-constructs and ExclusiveAccess from @a other. The mutex, data pointer, and state of @a other is
* transferred to the newly constructed instance. After the move, @a other will no longer manage the mutex,
Expand All @@ -44,43 +44,43 @@ namespace Qx
//-Destructor------------------------------------------------------------------------------------------------
//Public:
/*!
* @fn ExclusiveAccess<Type, Mutex>::~ExclusiveAccess()
* @fn ExclusiveAccess<AccessType, Mutex>::~ExclusiveAccess()
*
* Destroys the ExclusiveAccess and unlocks the mutex provided by the constructor if it's still locked.
*/

//-Class Functions----------------------------------------------------------------------------------------------
//-Instance Functions----------------------------------------------------------------------------------------------
//Public:
/*!
* @fn void ExclusiveAccess<Type, Mutex>::isLocked() const
* @fn void ExclusiveAccess<AccessType, Mutex>::isLocked() const
*
* Returns @c true if this ExclusiveAccess is currently locking its associated mutex; otherwise, returns
* @c false.
*/

/*!
* @fn void ExclusiveAccess<Type, Mutex>::mutex() const
* @fn void ExclusiveAccess<AccessType, Mutex>::mutex() const
*
* Returns the mutex on which the ExclusiveAccess is operating.
*/

/*!
* @fn void ExclusiveAccess<Type, Mutex>::relock()
* @fn void ExclusiveAccess<AccessType, Mutex>::relock()
*
* Relocks an unlocked ExclusiveAccess.
*
* @sa unlock().
*/

/*!
* @fn void ExclusiveAccess<Type, Mutex>::swap(ExclusiveAccess& other)
* @fn void ExclusiveAccess<AccessType, Mutex>::swap(ExclusiveAccess& other)
*
* Swaps the mutex, data pointer, and state of this ExclusiveAccess with @a other. This operation
* is very fast and never fails.
*/

/*!
* @fn void ExclusiveAccess<Type, Mutex>::unlock()
* @fn void ExclusiveAccess<AccessType, Mutex>::unlock()
*
* Unlocks this ExclusiveAccess. You can use relock() to lock it again. It does not need to be
* locked when destroyed.
Expand All @@ -89,43 +89,43 @@ namespace Qx
*/

/*!
* @fn AccessType* ExclusiveAccess<Type, Mutex>::access()
* @fn AccessType* ExclusiveAccess<AccessType, Mutex>::access()
*
* Returns a pointer to the data the ExclusiveAccess is providing access to.
*/

/*!
* @fn const AccessType* ExclusiveAccess<Type, Mutex>::access() const
* @fn const AccessType* ExclusiveAccess<AccessType, Mutex>::access() const
*
* @overload
*/

/*!
* @fn AccessType& ExclusiveAccess<Type, Mutex>::operator*()
* @fn AccessType& ExclusiveAccess<AccessType, Mutex>::operator*()
*
* Returns a reference to the data the ExclusiveAccess is providing access to.
*/

/*!
* @fn const AccessType& ExclusiveAccess<Type, Mutex>::operator*() const
* @fn const AccessType& ExclusiveAccess<AccessType, Mutex>::operator*() const
*
* @overload
*/

/*!
* @fn AccessType* ExclusiveAccess<Type, Mutex>::operator->()
* @fn AccessType* ExclusiveAccess<AccessType, Mutex>::operator->()
*
* Provides convenient access to the members of @a DataType for the accessible data.
*/

/*!
* @fn const AccessType* ExclusiveAccess<Type, Mutex>::operator->() const
* @fn const AccessType* ExclusiveAccess<AccessType, Mutex>::operator->() const
*
* @overload
*/

/*!
* @fn void ExclusiveAccess<Type, Mutex>::operator=(ExclusiveAccess&& other)
* @fn void ExclusiveAccess<AccessType, Mutex>::operator=(ExclusiveAccess&& other)
*
* Move-assigns @a other onto this ExclusiveAccess. If this ExclusiveAccess was holding onto a
* locked mutex before the assignment, the mutex will be unlocked. The mutex, data pointer, and
Expand Down
58 changes: 58 additions & 0 deletions lib/core/src/qx-threadsafesingleton.dox
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
namespace Qx
{
//===============================================================================================================
// ThreadSafeSingleton
//===============================================================================================================

/*!
* @class ThreadSafeSingleton qx/core/qx-threadsafesingleton.h
* @ingroup qx-core
*
* @brief The ThreadSafeSingleton template class provides access to a static singleton instance in a thread-safe
* manner.
*
* This class allows one to easily utilize the singleton pattern while ensuring that access to the global shared
* instance remains thread-safe. This is achieved by providing basic scaffolding through a base class from which
* the actual singleton should derive. The instance() method provides a mutex protected pointer to the
* singleton instance via Qx::ExclusiveAccess. By declaring the final class' constructor as private, and accessing
* the class through instance(), one can be certain that the instance is only ever being used by one thread at a
* time.
*
* If code in your singleton calls external code that in turn results in instance() being called again
* from within the same thread, be sure to use QRecursiveMutex when instantiating this template or else you
* may cause a dead-lock.
*
* The following is a complete example for using Qx::ThreadSafeSingleton:
*
* @snippet qx-threadsafesingleton.cpp 0
*/

//-Constructor----------------------------------------------------------------------------------------------
//Protected:
/*!
* @fn ThreadSafeSingleton<Singleton, Mutex>::ThreadSafeSingleton()
*
* Constructs a ThreadSafeSingleton.
*
* @sa instance().
*/

//-Class Functions----------------------------------------------------------------------------------------------
//Public:
/*!
* @fn Qx::ExclusiveAccess<Singleton, QMutex> ThreadSafeSingleton<Singleton, Mutex>::instance()
*
* Returns a handle to the singleton instance.
*/

//-Macros----------------------------------------------------------------------------------------------------------
/*!
* @def QX_THREAD_SAFE_SINGLETON(Singleton)
*
* This macro must be used within the class definition of any singleton that derived from this class, similar to
* Q_OBJECT for classes derived from QObject.
*
* The argument is to be the name of the derived class itself (similar to CRTP).
*/

}

0 comments on commit 6fc9c4e

Please sign in to comment.