Design of a General-Purpose Multithreading Middleware in Qt C++

For multithreading collaborative scenarios, design ageneral-purpose multithreading middleware that supports thread state management (initialization → startup → running → waiting → stopping), dependency awareness, ordered switching, and error handling. Below is the complete implementation plan.

The design goals of the middleware — High versatility: Adapt to various types of threads, reducing expansion costs

  • Versatility: Supports the integration and management of different functional threads (such as data acquisition, algorithm processing, device control).

  • State Awareness: Real-time monitoring of all thread states (uninitialized/ready/running/waiting/stopped/error).

  • Ordered Control: Supports switching thread states based on dependency relationships (e.g., thread B starts after thread A completes).

  • Safe Switching: Ensures that thread state transitions are valid (e.g., prevents uninitialized threads from starting).

  • Error Propagation: Automatically triggers global stop or downgrade handling when any thread encounters an exception.

  • This middleware abstracts complex multithreading collaboration issues into standardized state management and dependency coordination processes throughstate machine constraints, dependency resolution, and signal-slot communication, ensuring thesafety and orderliness of thread execution in practical use cases, while reducing functional expansion and maintenance costs through generalized design.


  • Core Architecture Design

Design of a General-Purpose Multithreading Middleware in Qt C++

1. Layered Architecture Diagram

Application Layer: Integrates specific functional threads (such as data acquisition, processing), managing states through middleware interfaces.

  • Core Layer: Implements core logic such as state management, dependency resolution, notification triggering, and exception handling.

  • Support Layer: Implements inter-thread communication and synchronization based on Qt’s native mechanisms (threads, signal-slot, synchronization primitives).

Core Module Implementation

1. Thread State Enumeration and State Machine

Defines the full lifecycle states of a thread and constrains valid transitions through a state machine.

// ThreadState.h#pragma once
// Thread state enumeration (full lifecycle)enum class ThreadState {    Uninitialized,  // Uninitialized (initial state)    Ready,          // Ready (initialization complete, waiting to start)    Running,        // Running (executing tasks)    Waiting,        // Waiting (dependent on other threads to complete)    Stopped,        // Stopped (actively or passively stopped)    Error           // Error (runtime exception)};
// State transition rules (valid transition paths)const QMap<ThreadState, QVector<ThreadState>> VALID_TRANSITIONS = {    {ThreadState::Uninitialized, {ThreadState::Ready}},       // Initialization → Ready    {ThreadState::Ready, {ThreadState::Running, ThreadState::Stopped}}, // Ready → Running/Stopped    {ThreadState::Running, {ThreadState::Waiting, ThreadState::Stopped, ThreadState::Error}}, // Running → Waiting/Stopped/Error    {ThreadState::Waiting, {ThreadState::Running, ThreadState::Stopped}}, // Waiting → Running/Stopped    {ThreadState::Stopped, {ThreadState::Ready, ThreadState::Uninitialized}}, // Stopped → Ready/Uninitialized (reset)    {ThreadState::Error, {ThreadState::Stopped}} // Error → Stopped (non-recoverable)};

2. Thread Descriptor (Functional Thread Metadata)

Encapsulates core information of the thread (name, type, dependencies, state, etc.), serving as the smallest unit managed by the middleware.

// ThreadDescriptor.h#pragma once#include <QString>#include <QList>#include <QVariantMap>#include "ThreadState.h"
// Functional thread metadata descriptorstruct ThreadDescriptor {    QString name;                  // Unique thread name (identifier)    QString type;                  // Thread type (e.g., "DataAcquisition", "Algorithm")    QList<QString> dependencies;   // Names of dependent threads (must start first)    ThreadState state;             // Current state    QVariantMap parameters;        // Thread parameters (e.g., sampling frequency, algorithm threshold)    QObject* instance;             // Pointer to thread instance (subclass of QThread)
    // Constructor    ThreadDescriptor(const QString& name, const QString& type,                      const QList<QString>& deps = {},                      const QVariantMap& params = {})        : name(name), type(type), dependencies(deps), state(ThreadState::Uninitialized),           parameters(params), instance(nullptr) {}};

3. Core Class of Thread Middleware (ThreadMiddleware)

Globally manages all threads, implementing state switching, dependency resolution, notification triggering, and other core logic.

// ThreadMiddleware.h#pragma once#include <QObject>#include <QMap>#include <QList>#include <QMutex>#include <QWaitCondition>#include "ThreadDescriptor.h"
class ThreadMiddleware : public QObject {    Q_OBJECT
public:    // Singleton pattern (global unique middleware instance)    static ThreadMiddleware* instance() {        static ThreadMiddleware middleware;        return &middleware;    }
    // Disable copy/move    ThreadMiddleware(const ThreadMiddleware&) = delete;    ThreadMiddleware& operator=(const ThreadMiddleware&) = delete;
    // Register functional thread (requires thread instance and descriptor)    bool registerThread(QObject* threadInstance, const ThreadDescriptor& descriptor);
    // Initialize all threads (in dependency order)    bool initializeThreads();
    // Start specified thread (trigger state switch: Ready→Running)    bool startThread(const QString& threadName);
    // Stop specified thread (trigger state switch: Running→Stopped)    bool stopThread(const QString& threadName);
    // Wait for specified thread to complete (block current thread until target state)    bool waitForThread(const QString& threadName, ThreadState targetState, int timeoutMs = 5000);
    // Query thread state (thread-safe)    ThreadState getThreadState(const QString& threadName) const;
    // Trigger thread state switch (internal use, externally triggered by signals)    void notifyThreadStateChanged(const QString& threadName, ThreadState newState);
signals:    // Thread state change notification (external subscription)    void threadStateChanged(const QString& threadName, ThreadState newState);    // Thread initialization complete notification    void threadInitialized(const QString& threadName);    // Thread error notification    void threadErrorOccurred(const QString& threadName, const QString& errorMsg);
private:    ThreadMiddleware() = default;  // Private constructor for singleton
    // Internal methods    bool validateTransition(const QString& threadName, ThreadState newState) const;    void updateThreadState(const QString& threadName, ThreadState newState);    QList<QString> getDependentThreads(const QString& threadName) const;    void processWaitingThreads();  // Process threads in waiting state (check if dependencies are met)
    QMap<QString, ThreadDescriptor> m_threads;  // Registered thread descriptors    mutable QMutex m_mutex;                      // Protect shared data    QWaitCondition m_waitCond;                   // Condition variable (wait for state change)};

4. Base Class for Functional Threads (BaseFunctionThread)

All functional threads must inherit from this class, implementing specific business logic while decoupling from the middleware.

// BaseFunctionThread.h#pragma once#include <QThread>#include <QObject>#include "ThreadMiddleware.h"
class BaseFunctionThread : public QThread {    Q_OBJECT
public:    explicit BaseFunctionThread(const QString& threadName, QObject* parent = nullptr)        : QThread(parent), m_threadName(threadName) {}
    // Initialize thread (override to implement resource loading)    virtual bool initialize() {        qInfo() << "Thread" << m_threadName << "initialized";        emit initialized();  // Notify middleware that initialization is complete        return true;    }
    // Start thread task (override to implement core logic)    virtual void startTask() {        qInfo() << "Thread" << m_threadName << "started, beginning task execution...";        // Simulate task execution (replace with business logic)        for (int i = 0; i < 5; ++i) {            QThread::msleep(1000);  // Simulate time-consuming operation            qInfo() << "Thread" << m_threadName << "executing step" << i;        }        emit taskCompleted();  // Notify middleware that the task is complete    }
    // Stop thread task (override to implement resource release)    virtual void stopTask() {        qInfo() << "Thread" << m_threadName << "stopped, releasing resources...";    }
    // Get thread name    QString name() const { return m_threadName; }
signals:    // Initialization complete signal (triggers middleware state switch)    void initialized();    // Task complete signal (triggers middleware state switch)    void taskCompleted();    // Error signal (triggers middleware exception handling)    void errorOccurred(const QString& errorMsg);
protected:    QString m_threadName;  // Thread name (consistent with middleware descriptor)};

Key Function Implementations

1. Thread Registration and Initialization

Register threads through the middleware, initializing in dependency order to ensure dependent threads start first.

// ThreadMiddleware.cppbool ThreadMiddleware::registerThread(QObject* threadInstance, const ThreadDescriptor& descriptor) {    QMutexLocker locker(&m_mutex);    if (m_threads.contains(descriptor.name)) {        qWarning() << "Thread" << descriptor.name << "already registered";        return false;    }
    // Bind thread instance and descriptor    ThreadDescriptor& desc = m_threads[descriptor.name];    desc.instance = threadInstance;    desc.parameters = descriptor.parameters;    desc.dependencies = descriptor.dependencies;    desc.state = ThreadState::Uninitialized;    return true;}
bool ThreadMiddleware::initializeThreads() {    QMutexLocker locker(&m_mutex);    // Initialize in topological order (threads without dependencies first)    QList<QString> initOrder;    for (const auto& [name, desc] : m_threads) {        if (desc.dependencies.isEmpty()) {            initOrder.append(name);        }    }
    // Recursively handle dependencies (example simplified, actual needs topological sorting)    for (const QString& name : initOrder) {        ThreadDescriptor& desc = m_threads[name];        if (desc.state != ThreadState::Uninitialized) continue;        // Call thread initialization interface        if (BaseFunctionThread* thread = qobject_cast<BaseFunctionThread*>(desc.instance)) {            if (!thread->initialize()) {                qCritical() << "Thread" << name << "initialization failed";                desc.state = ThreadState::Error;                emit threadErrorOccurred(name, "initialization failed");                return false;            }            desc.state = ThreadState::Ready;            emit threadStateChanged(name, ThreadState::Ready);            emit threadInitialized(name);        }    }    return true;}

2. Thread State Switching and Dependency Checking

Check legality before state switching, automatically entering waiting state if dependent threads are not completed.

// ThreadMiddleware.cppbool ThreadMiddleware::validateTransition(const QString& threadName, ThreadState newState) const {    const ThreadDescriptor& desc = m_threads.value(threadName);    if (!m_threads.contains(threadName)) {        qWarning() << "Thread" << threadName << "not registered";        return false;    }
    // Check if state transition is valid    if (!VALID_TRANSITIONS.value(desc.state).contains(newState)) {        qWarning() << "Thread" << threadName << "invalid state transition:"                   << stateToString(desc.state) << "→" << stateToString(newState);        return false;    }
    // Check if dependencies are met (if needed)    if (newState == ThreadState::Running) {        for (const QString& dep : desc.dependencies) {            if (m_threads.value(dep).state != ThreadState::Stopped) {                qWarning() << "Thread" << threadName << "dependent thread" << dep << "not stopped";                return false;            }        }    }
    return true;}
void ThreadMiddleware::updateThreadState(const QString& threadName, ThreadState newState) {    QMutexLocker locker(&m_mutex);    if (!validateTransition(threadName, newState)) return;    ThreadDescriptor& desc = m_threads[threadName];    desc.state = newState;    emit threadStateChanged(threadName, newState);    // Trigger dependency thread check (e.g., wake up waiting threads when current thread completes)    if (newState == ThreadState::Stopped) {        processWaitingThreads();    }}

3. Thread Waiting and Synchronization

Implement thread waiting through condition variables until dependent threads complete or timeout.

// ThreadMiddleware.cppbool ThreadMiddleware::waitForThread(const QString& threadName, ThreadState targetState, int timeoutMs) {    QMutexLocker locker(&m_mutex);    const ThreadDescriptor& desc = m_threads.value(threadName);    if (!m_threads.contains(threadName)) {        qWarning() << "Thread" << threadName << "not registered";        return false;    }
    // Wait for state change or timeout    while (desc.state != targetState) {        if (!m_waitCond.wait(&m_mutex, timeoutMs)) {            qWarning() << "Waiting for thread" << threadName << "timeout (target state:" << stateToString(targetState) << ")";            return false;        }    }
    return true;}
void ThreadMiddleware::processWaitingThreads() {    QMutexLocker locker(&m_mutex);    for (auto& [name, desc] : m_threads) {        if (desc.state == ThreadState::Waiting) {            // Check if all dependencies are complete            bool dependenciesMet = true;            for (const QString& dep : desc.dependencies) {                if (m_threads.value(dep).state != ThreadState::Stopped) {                    dependenciesMet = false;                    break;                }            }            if (dependenciesMet) {                updateThreadState(name, ThreadState::Running);  // Trigger running            }        }    }}

Usage Example

1. Define Functional Thread (Data Acquisition Thread)

// DataAcquisitionThread.h#pragma once#include "BaseFunctionThread.h"
class DataAcquisitionThread : public BaseFunctionThread {    Q_OBJECTpublic:    explicit DataAcquisitionThread(const QString& name, QObject* parent = nullptr)        : BaseFunctionThread(name, parent) {}
protected:    void run() override {        if (!initialize()) {  // Initialization (interact with middleware)            emit errorOccurred("initialization failed");            return;        }        startTask();  // Execute task (interact with middleware)    }
    void startTask() override {        QThread::msleep(2000);  // Simulate data acquisition (2 seconds)        emit taskCompleted();  // Notify middleware that the task is complete    }};

2. Integrate Middleware in Main Program

// main.cpp#include <QCoreApplication>#include <QDebug>#include "ThreadMiddleware.h"#include "DataAcquisitionThread.h"#include "AlgorithmProcessingThread.h"
int main(int argc, char *argv[]) {    QCoreApplication app(argc, argv);
    // Get middleware instance    ThreadMiddleware* middleware = ThreadMiddleware::instance();
    // Create and register data acquisition thread    DataAcquisitionThread* daThread = new DataAcquisitionThread("DataAcquisition");    ThreadDescriptor daDesc("DataAcquisition", "DataAcquisition");    middleware->registerThread(daThread, daDesc);
    // Create and register algorithm processing thread (depends on data acquisition thread)    AlgorithmProcessingThread* apThread = new AlgorithmProcessingThread("AlgorithmProcessing");    ThreadDescriptor apDesc("AlgorithmProcessing", "AlgorithmProcessing", {"DataAcquisition"});    middleware->registerThread(apThread, apDesc);
    // Connect middleware signals    QObject::connect(middleware, &ThreadMiddleware::threadStateChanged, [](const QString& name, ThreadState state) {        qDebug() << "Thread" << name << "state changed to:" << stateToString(state);    });    QObject::connect(middleware, &ThreadMiddleware::allThreadsStopped, []() {        qDebug() << "All threads have stopped, exiting program";        QCoreApplication::quit();    });
    // Initialize and start threads    if (middleware->initializeThreads()) {        middleware->startThread("DataAcquisition");  // Start data acquisition thread        middleware->startThread("AlgorithmProcessing");  // Start algorithm processing thread (automatically waits for data acquisition to complete)    }
    return app.exec();}

Conclusion

This middleware provides a general, safe, and flexible management solution for multithreading collaborative scenarios by constraining thread lifecycles through a state machine, implementing inter-thread communication via signal-slot, and ensuring thread safety with mutexes and condition variables. It is suitable for fields requiring high-precision thread collaboration, such as industrial control, data processing, and device linkage.

Design of a General-Purpose Multithreading Middleware in Qt C++

Leave a Comment