工业软件架构 - 事件驱动 - 3
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 之前完成。
- 任务类定义
首先,我们定义每个任务类,这些任务将通过 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; };
- 任务执行
在主程序中,我们使用 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(); }
- 运行原理
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 协调任务执行顺序,并在程序退出时安全终止所有监控线程。