Skip to content

Commit

Permalink
Merge pull request #143 from oblivioncth/implementation/general_worke…
Browse files Browse the repository at this point in the history
…r_thread

Add general worker thread to minimize threads started by lib
  • Loading branch information
oblivioncth authored Nov 11, 2024
2 parents b36e7f4 + 9c40211 commit 3e877cc
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 0 deletions.
2 changes: 2 additions & 0 deletions lib/core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ qx_add_component("Core"
qx-systemerror.cpp
qx-systemerror_linux.cpp
qx-systemerror_win.cpp
__private/qx-generalworkerthread.h
__private/qx-generalworkerthread.cpp
__private/qx-internalerror.cpp
__private/qx-processwaiter.h
__private/qx-processwaiter.cpp
Expand Down
87 changes: 87 additions & 0 deletions lib/core/src/__private/qx-generalworkerthread.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Unit Include
#include "qx-generalworkerthread.h"

// Qt Includes
#include <QCoreApplication>

/*! @cond */
namespace Qx
{

//===============================================================================================================
// GeneralWorkerThread
//===============================================================================================================

//-Constructor--------------------------------------------------------------------
//Public:
GeneralWorkerThread::GeneralWorkerThread() :
mWorkerCount(0)
{
/* mThread is the only QObject member of this class. We need to move it to the main thread because
* the manager can be created in any thread since it's done by RAII and UB occurs if a Object (in this case
* the QThread) continues to be used if the thread it belongs to is shutdown. moveToThread() already checks
* if this is the main thread and results in a no-op if so. Also, QThread's public methods are protected
* by a mutex, so it's safe to interact with it from which ever thread is accessing the manager.
*/
QThread* mainThread = QCoreApplication::instance()->thread();
if(!mainThread) [[unlikely]]
{
// It's documented that you're not supposed to use QObjects before QCoreAppliation is created,
// but check explicitly anyway
qCritical("Cannot use QObject's before QCoreApplication is created!");
}

mThread.moveToThread(mainThread);
}

//-Destructor--------------------------------------------------------------------
//Public:
GeneralWorkerThread::~GeneralWorkerThread()
{
if(mThread.isRunning())
stopThread(true);
}

//-Instance Functions--------------------------------------------------------------------------------------------
//Private:
void GeneralWorkerThread::startThread()
{
Q_ASSERT(!mThread.isRunning());
mThread.start(QThread::LowPriority); // The work here should be on the lighter side
}

void GeneralWorkerThread::stopThread(bool wait)
{
Q_ASSERT(mThread.isRunning());
mThread.quit();
if(wait)
mThread.wait();
}

void GeneralWorkerThread::workerDestroyed()
{
if(!--mWorkerCount)
stopThread();
}

//Public:
void GeneralWorkerThread::moveTo(QObject* object)
{
if(!mWorkerCount++)
startThread();

object->moveToThread(&mThread);

// Worker management

/* Have worker killed if it still exists when thread is being shutdown;
*
* QThread docs note that deferred deletions still occur after finished is emitted, so this is possible
* (this use case is explicitly mentioned).
*/
QObject::connect(&mThread, &QThread::finished, object, &QObject::deleteLater);
QObject::connect(object, &QObject::destroyed, object, []{ GeneralWorkerThread::instance()->workerDestroyed(); }); // Notify of destruction
}

}
/*! @endcond */
53 changes: 53 additions & 0 deletions lib/core/src/__private/qx-generalworkerthread.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#ifndef QX_GENERALWORKERTHREAD_H
#define QX_GENERALWORKERTHREAD_H

// Qt Includes
#include <QThread>

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

/*! @cond */
namespace Qx
{

/* A dedicated thread for Qx worker objects so that we can be sure the thread they run on is never blocked
* for long periods. Automatically starts up when objects are added, and stops when the last one is removed.
*
* Although this is called "GeneralWorkerThread", it's more so its manager. The real thread is created
* by QThread. So, this class can be called anywhere, at anytime; therefore, we make it TSS
*/
class GeneralWorkerThread : public ThreadSafeSingleton<GeneralWorkerThread>
{
QX_THREAD_SAFE_SINGLETON(GeneralWorkerThread);
//-Instance Variables-------------------------------------------------------------------------------------------------
private:
QThread mThread;
uint mWorkerCount;

//-Constructor-------------------------------------------------------------------------------------------------
private:
GeneralWorkerThread();

//-Destructor-------------------------------------------------------------------------------------------------
public:
~GeneralWorkerThread();

//-Class Functions---------------------------------------------------------------------------------------------
public:


//-Instance Functions--------------------------------------------------------------------------------------------
private:
void startThread();
void stopThread(bool wait = false);
void workerDestroyed();

public:
void moveTo(QObject* object);
};

}
/*! @endcond */

#endif // QX_GENERALWORKERTHREAD_H

0 comments on commit 3e877cc

Please sign in to comment.