Bootstrap

工业软件架构3:(QT和C++实现)

1. 并行复杂任务

当某些耗时操作本身需要开几个子线程来完成,通常意味着这些操作非常复杂,可能涉及多步计算或并行处理。
在这种情况下,需要更加精细地管理线程,以确保操作的并发性和主线程的响应性。以下是处理这种情况的推荐方法和架构设计。

1.1 使用 QThreadPool 和 QRunnable 进行任务并行化

QThreadPool 和 QRunnable 是 Qt 提供的工具,用于高效地管理并发任务。你可以将耗时操作的不同步骤封装为独立的任务,并将它们提交给 QThreadPool 来并行执行。

假设我们有一个复杂的操作需要同时处理多个子任务,每个子任务都需要在不同的线程中运行。

class ComplexTask : public QRunnable 
{
public:
    ComplexTask(int taskId) : taskId(taskId) {}

    void run() override 
    {
        // 模拟耗时任务
        qDebug() << "Task" << taskId << "started.";
        QThread::sleep(2);  // 模拟长时间运行
        qDebug() << "Task" << taskId << "completed.";
    }

private:
    int taskId;
};

主任务可以将多个 ComplexTask 提交给 QThreadPool 来并行执行:

class ComplexOperation : public QObject {
    Q_OBJECT

public:
    ComplexOperation(QObject *parent = nullptr) : QObject(parent) {}

    void execute() 
    {
        QThreadPool *pool = QThreadPool::globalInstance();
        for (int i = 0; i < 5; ++i) 
        {
            pool->start(new ComplexTask(i));
        }

        // 等待所有任务完成(可选)
        pool->waitForDone();
        emit operationCompleted();
    }

signals:
    void operationCompleted();
};

1.2 使用 QFuture 和 QFutureWatcher 进行异步任务管理

QFuture 和 QFutureWatcher 提供了一种更高级的方式来管理和监控异步任务,特别适合在主线程中等待或监听多个子线程任务的完成状态。

class ParallelTaskManager : public QObject
 {
    Q_OBJECT

public:
    ParallelTaskManager(QObject *parent = nullptr) : QObject(parent) {}

    void executeComplexOperation() 
    {
        QVector<QFuture<void>> futures;
        for (int i = 0; i < 5; ++i)
         {
            QFuture<void> future = QtConcurrent::run(this, &ParallelTaskManager::performTask, i);
            futures.append(future);
        }

        QFutureWatcher<void> *watcher = new QFutureWatcher<void>(this);
        connect(watcher, &QFutureWatcher<void>::finished, this, &ParallelTaskManager::onAllTasksCompleted);
        watcher->setFuture(QtConcurrent::run([futures]()
         {
            for (auto &future : futures) 
            {
                future.waitForFinished();
            }
        }));
    }
private:
    void performTask(int taskId)
     {
        qDebug() << "Performing task" << taskId;
        QThread::sleep(2);  // 模拟耗时任务
        qDebug() << "Task" << taskId << "done";
    }

private slots:
    void onAllTasksCompleted()
     {
        qDebug() << "All tasks completed.";
        emit complexOperationCompleted();
    }
    
signals:
    void complexOperationCompleted();
};

1.3 结合命令模式

在这种复杂场景中,结合命令模式可以有效地组织代码,确保任务执行的灵活性和可维护性。命令对象可以封装任务的执行逻辑,包括启动多个子线程的操作。

class ComplexOperationCommand : public Command 
{
public:
    ComplexOperationCommand(ParallelTaskManager *taskManager)
        : taskManager(taskManager) {}

    void execute() override
     {
        taskManager->executeComplexOperation();
    }

private:
    ParallelTaskManager *taskManager;
};

1.4 错误处理与状态更新

在处理多个并行任务时,还需要考虑如何处理错误和更新任务状态。可以使用信号与槽机制来实现任务的状态监控与错误处理。

void ParallelTaskManager::executeComplexOperation()
 {
    QVector<QFuture<void>> futures;
    for (int i = 0; i < 5; ++i) 
    {
        QFuture<void> future = QtConcurrent::run(this, &ParallelTaskManager::performTask, i);
        futures.append(future);
    }

    QFutureWatcher<void> *watcher = new QFutureWatcher<void>(this);
    connect(watcher, &QFutureWatcher<void>::finished, this, &ParallelTaskManager::onAllTasksCompleted);
    connect(watcher, &QFutureWatcher<void>::progressValueChanged, this, &ParallelTaskManager::onTaskProgress);
    watcher->setFuture(QtConcurrent::run([futures]()
     {
        for (auto &future : futures)
         {
            future.waitForFinished();
        }
    }));
}

void ParallelTaskManager::onTaskProgress(int progress) 
{
    // 更新进度或处理错误
    qDebug() << "Task progress:" << progress;
}

1.5 总结

  • QThreadPool 和 QRunnable: 适合简单的并行任务管理,直接将任务分配给线程池执行。
  • QFuture 和 QFutureWatcher: 提供了更高级的任务监控和管理方式,适合需要监听任务完成状态的场景。
  • 命令模式: 可以将复杂的任务逻辑封装为命令对象,结合线程池或异步任务框架执行,增强代码的可维护性。
  • 错误处理与状态监控: 通过信号与槽机制实时监控任务状态,处理错误,确保系统的稳定性和响应性。

2. 多线程任务

当几个子线程执行不同的操作,甚至包含类似监控子线程的任务时,设计和管理这些线程的方式会更加复杂。下面是如何处理这种情况的推荐架构设计:

2.1. 任务类型和线程的划分

首先,明确每个子线程的任务类型。这些任务可能包括:

  • 计算任务: 耗时计算或数据处理任务,需要在后台线程中执行。
  • I/O 任务: 文件读取、网络请求等 I/O 密集型任务。
  • 监控任务: 持续运行的任务,例如传感器状态监控、硬件状态监控。

根据任务的不同性质,将线程划分为以下几类:

  • 短期任务线程: 适用于一次性执行的计算或 I/O 任务,完成后即可销毁线程。
  • 长期任务线程(监控线程): 持续运行的线程,负责实时监控系统状态或数据变化。
  • 线程池任务: 使用线程池管理的任务,适合多个并行的短期任务。

2.2 监控任务的处理

对于监控任务,通常需要使用 QThread 来创建一个专用的线程类,该线程会在后台持续运行,并定期执行监控操作。

class MonitoringThread : public QThread 
{
    Q_OBJECT

public:
    MonitoringThread(QObject *parent = nullptr) : QThread(parent) {}
protected:
    void run() override 
    {
        while (!isInterruptionRequested())
         {
            // 执行监控操作
            monitorSensors();
            QThread::sleep(1);  // 每秒执行一次监控操作
        }
    }

private:
    void monitorSensors()
     {
        // 模拟传感器监控逻辑
        qDebug() << "Monitoring sensors...";
    }
};

启动和管理这个监控线程:

MonitoringThread *monitorThread = new MonitoringThread();
monitorThread->start();

2.3 复杂任务的并行处理

对于复杂任务,需要在命令模式中进行封装,并将这些任务分配给不同的子线程执行。

示例:
假设有一个复杂的操作需要并行执行多个子任务:

class SubTask1 : public QRunnable 
{
public:
    void run() override 
    {
        // 执行子任务 1
        qDebug() << "SubTask 1 executing...";
        QThread::sleep(2);
        qDebug() << "SubTask 1 completed.";
    }
};

class SubTask2 : public QRunnable
 {
public:
    void run() override
     {
        // 执行子任务 2
        qDebug() << "SubTask 2 executing...";
        QThread::sleep(3);
        qDebug() << "SubTask 2 completed.";
    }
};

class ComplexOperationCommand : public Command
 {
public:
    void execute() override 
    {
        QThreadPool *pool = QThreadPool::globalInstance();
        pool->start(new SubTask1());
        pool->start(new SubTask2());

        // 等待所有任务完成(可选)
        pool->waitForDone();
        qDebug() << "All sub-tasks completed.";
    }
};

2.4. 管理和协调多个线程

当有多个不同类型的线程在运行时,需要确保线程之间的协调和资源管理。以下是一些推荐的管理方法:

使用信号与槽进行线程间通信
在 Qt 中,使用信号与槽机制可以轻松实现线程之间的通信,避免线程安全问题。

class MonitoringThread : public QThread 
{
    Q_OBJECT

signals:
    void sensorDataUpdated(int sensorId, double value);

protected:
    void run() override
     {
        while (!isInterruptionRequested())
         {
            double value = monitorSensors();
            emit sensorDataUpdated(1, value);
            QThread::sleep(1);
        }
    }

private:
    double monitorSensors() 
    {
        // 返回模拟传感器数据
        return qrand() % 100 / 10.0;
    }
};

2.5 使用 QWaitCondition 和 QMutex 控制任务执行

当你有多个线程需要按照严格的顺序执行时,可以使用 QMutex 来保护共享资源,并使用 QWaitCondition 让一个线程等待另一个线程的完成信号。这种机制确保了多个线程能够按照你期望的顺序运行。

QWaitCondition 和 QMutex 是 Qt 提供的用于线程同步的工具。

  • QMutex: 互斥锁,用于保护共享资源,确保在多线程环境中,只有一个线程可以访问某一共享资源。
  • QWaitCondition: 条件变量,用于线程间的协调。它可以让一个线程等待某个条件的满足,并在条件满足时通知线程继续执行。

示例:三个需要严格顺序执行的任务
假设我们有三个任务 Task1、Task2 和 Task3,它们需要严格按顺序执行。即 Task1 必须在 Task2 之前完成,而 Task2 必须在 Task3 之前完成。

  1. 任务类定义
    首先,我们定义每个任务类,这些任务将通过 QRunnable 来运行:
    #include <QCoreApplication>
    #include <QThreadPool>
    #include <QRunnable>
    #include <QWaitCondition>
    #include <QMutex>
    #include <QMutexLocker>
    #include <QDebug>
    
    // 任务1
    class Task1 : public QRunnable
     {
    public:
    	Task1(QMutex *mutex, QWaitCondition *cond1)
       		: mutex(mutex), cond1(cond1) {}
    
    	void run() override
    	 {
        	QMutexLocker locker(mutex);
        	qDebug() << "Task 1 started";
        	QThread::sleep(1);  // 模拟任务1耗时
        	qDebug() << "Task 1 completed";
        	cond1->wakeOne();  // 通知任务2可以开始了
    	}
    
    private:
    	QMutex *mutex;
    	QWaitCondition *cond1;
    };
    
    // 任务2
    class Task2 : public QRunnable
     {
    public:
    	Task2(QMutex *mutex, QWaitCondition *cond1, QWaitCondition *cond2)
        	: mutex(mutex), cond1(cond1), cond2(cond2) {}
    
    	void run() override 
    	{
        	QMutexLocker locker(mutex);
        	cond1->wait(mutex);  // 等待任务1完成
        	qDebug() << "Task 2 started";
        	QThread::sleep(1);  // 模拟任务2耗时
        	qDebug() << "Task 2 completed";
        	cond2->wakeOne();  // 通知任务3可以开始了
    	}
    
    private:
    	QMutex *mutex;
    	QWaitCondition *cond1;
    	QWaitCondition *cond2;
    };
    
    // 任务3
    class Task3 : public QRunnable
     {
    public:
    	Task3(QMutex *mutex, QWaitCondition *cond2)
        	: mutex(mutex), cond2(cond2) {}
    
    	void run() override 
    	{
        	QMutexLocker locker(mutex);
       		cond2->wait(mutex);  // 等待任务2完成
        	qDebug() << "Task 3 started";
        	QThread::sleep(1);  // 模拟任务3耗时
       		qDebug() << "Task 3 completed";
    	}
    
    private:
    	QMutex *mutex;
    	QWaitCondition *cond2;
    };
    
    1. 任务执行
      在主程序中,我们使用 QThreadPool 来启动这些任务,并确保它们按顺序执行。
    int main(int argc, char *argv[])
     {
    	QCoreApplication a(argc, argv);
    
    	QMutex mutex;
    	QWaitCondition cond1, cond2;
    
    	QThreadPool *pool = QThreadPool::globalInstance();
    
    	// 创建任务并设置顺序
    	Task1 *task1 = new Task1(&mutex, &cond1);
    	Task2 *task2 = new Task2(&mutex, &cond1, &cond2);
    	Task3 *task3 = new Task3(&mutex, &cond2);
    
    	// 提交任务到线程池
    	pool->start(task1);
    	pool->start(task2);
    	pool->start(task3);
    	return a.exec();
    }
    
    
    1. 运行原理
      Task1: 首先启动,完成后通过 cond1->wakeOne() 唤醒 Task2。
      Task2: 在 Task1 完成后开始执行,等待 cond1 条件满足,完成后通过 cond2->wakeOne() 唤醒 Task3。
      Task3: 在 Task2 完成后开始执行,等待 cond2 条件满足,然后开始执行。

2.6 管理监控线程的生命周期

在应用程序退出时,确保所有监控线程正确终止,以避免资源泄漏或线程悬挂。

// 在程序退出时终止监控线程
monitorThread->requestInterruption();
monitorThread->wait();  // 等待线程安全退出
delete monitorThread;

2.5 总结

  • 监控任务: 使用 QThread 实现长期运行的监控任务,专门的监控线程能够在后台持续运行,并定期执行检查。
  • 复杂操作: 使用 QThreadPool 或 QtConcurrent::run 处理并行的复杂操作,将多个子任务分配给不同的线程运行。
  • 线程间通信: 通过信号与槽机制实现线程间的通信和数据同步,避免线程安全问题。
  • 线程管理: 使用 QWaitCondition 和 QMutex 协调任务执行顺序,并在程序退出时安全终止所有监控线程。
;