Qt新手如何基于QThread实现阶乘计算GUI的启动/暂停/停止功能
阶乘计算任务的暂停、停止功能实现方案
现有代码的核心问题
- 全局定义的
factorialCalculator对象跨线程直接读写成员变量,没有同步机制,容易出现数据竞争 - 启动计算后立刻读取
resultFactorial值展示,此时子线程还未完成计算,必然读到初始空值 - 若要实现暂停功能,忙等循环会占满CPU资源,需要用Qt提供的同步原语实现无忙等的线程挂起
实现原理
无需使用危险的线程强制终止接口,通过两个状态标志+Qt同步原语即可实现全部需求:
- 停止标志:在计算循环的每一步检查标志位,置位后直接退出计算循环,实现安全停止
- 暂停标志:配合
QWaitCondition,当标志位为真时让线程进入阻塞等待状态,不占用CPU,收到唤醒指令后再继续执行 - 所有需要跨线程传递的状态、结果,全部通过Qt信号槽机制发送,自动完成跨线程数据同步,避免手动加锁的麻烦
代码改造步骤
1. 改造FactorialCalculator线程类
首先在头文件中加入同步组件、状态标志、对外操作接口、跨线程信号:
#ifndef FACTORIALCALCULATOR_H #define FACTORIALCALCULATOR_H #include <QThread> #include <QMutex> #include <QWaitCondition> class FactorialCalculator : public QThread { Q_OBJECT public: explicit FactorialCalculator(QObject *parent = nullptr); ~FactorialCalculator(); // 对外提供的操作接口 void pause(); void resume(); void stop(); int inputNumber; // 存储计算输入值 signals: // 跨线程通知GUI的信号 void calculationFinished(long long result); void calculationError(const QString &errMsg); void progressUpdated(int currentStep); protected: void run() override; private: long calculateFactorial(int input); QMutex m_mutex; // 保护跨线程访问的状态变量 QWaitCondition m_wait; // 实现线程挂起/唤醒 volatile bool m_isStopped; // 停止标志 volatile bool m_isPaused; // 暂停标志 }; #endif // FACTORIALCALCULATOR_H
2. 实现线程类逻辑
#include "factorialcalculator.h" #include <QDebug> FactorialCalculator::FactorialCalculator(QObject *parent) : QThread(parent) , m_isStopped(false) , m_isPaused(false) { } FactorialCalculator::~FactorialCalculator() { // 析构前确保线程完全退出,避免野线程 stop(); wait(); } void FactorialCalculator::pause() { QMutexLocker locker(&m_mutex); m_isPaused = true; } void FactorialCalculator::resume() { QMutexLocker locker(&m_mutex); m_isPaused = false; m_wait.wakeAll(); // 唤醒阻塞的线程 } void FactorialCalculator::stop() { QMutexLocker locker(&m_mutex); m_isStopped = true; m_isPaused = false; m_wait.wakeAll(); // 唤醒线程让它检查停止标志 } void FactorialCalculator::run() { // 每次启动计算前重置状态 { QMutexLocker locker(&m_mutex); m_isStopped = false; m_isPaused = false; } // 启动时缓存输入值,避免GUI后续修改输入造成混乱 int input = inputNumber; long long result = calculateFactorial(input); // 未被停止时才发送完成信号 if(!m_isStopped) { emit calculationFinished(result); } } long FactorialCalculator::calculateFactorial(int input) { if (input < 0) { emit calculationError("错误:负数无法计算阶乘"); return 0; } if (input > 20) // long long类型最大可存储20!的结果,超出会溢出 { emit calculationError("错误:输入数值过大,结果溢出"); return 0; } long long factorial = 1; for(int i = 1; i <= input; ++i) { // 每步计算前检查状态 { QMutexLocker locker(&m_mutex); // 检查停止标志,直接退出 if(m_isStopped) { return 0; } // 检查暂停标志,若处于暂停状态则阻塞等待 if(m_isPaused) { m_wait.wait(&m_mutex); // 被唤醒后再次检查停止标志 if(m_isStopped) { return 0; } } } // 正式执行单步计算 factorial *= i; emit progressUpdated(i); // 模拟计算耗时,避免大数值计算过快无法演示暂停效果 sleep(1); } return factorial; }
3. 改造主窗口逻辑
去掉原来的全局factorialCalculator和isSuspend变量,把计算器对象作为主窗口的成员,通过信号槽同步状态:
// mainwindow.cpp中的关键逻辑 MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this); m_calculator = new FactorialCalculator(this); // 连接跨线程信号,自动队列同步 connect(m_calculator, &FactorialCalculator::calculationFinished, this, [this](long long res){ ui->resultFactorial->setText(QString::number(res)); // 计算完成后更新按钮状态 ui->startFactorial->setEnabled(true); ui->pauseBtn->setEnabled(false); ui->stopBtn->setEnabled(false); }); connect(m_calculator, &FactorialCalculator::calculationError, this, [this](const QString &err){ ui->resultFactorial->setText(err); }); // 初始按钮状态 ui->pauseBtn->setEnabled(false); ui->stopBtn->setEnabled(false); } // 启动按钮槽函数 void MainWindow::on_startFactorial_clicked() { m_calculator->inputNumber = ui->lineEditFactorial->text().toInt(); ui->resultFactorial->clear(); m_calculator->start(); // 更新按钮状态 ui->startFactorial->setEnabled(false); ui->pauseBtn->setEnabled(true); ui->stopBtn->setEnabled(true); } // 暂停/继续按钮槽函数 void MainWindow::on_pauseBtn_clicked() { static bool isPaused = false; if(!isPaused) { m_calculator->pause(); ui->pauseBtn->setText("继续"); isPaused = true; } else { m_calculator->resume(); ui->pauseBtn->setText("暂停"); isPaused = false; } } // 停止按钮槽函数 void MainWindow::on_stopBtn_clicked() { m_calculator->stop(); m_calculator->wait(); // 等待线程完全退出 // 重置界面状态 ui->startFactorial->setEnabled(true); ui->pauseBtn->setEnabled(false); ui->pauseBtn->setText("暂停"); ui->stopBtn->setEnabled(false); ui->resultFactorial->setText("计算已停止"); } MainWindow::~MainWindow() { // 窗口关闭前确保线程安全退出 if(m_calculator->isRunning()) { m_calculator->stop(); m_calculator->wait(); } delete ui; }
关键注意事项
- 绝对禁止使用
QThread::terminate()接口停止线程,该方法会强制终止线程,可能造成锁未释放、资源泄漏等不可预期的问题 - 跨线程读写变量必须用
QMutex保护,不要直接在GUI线程读取子线程的计算结果,信号槽是Qt推荐的最安全的跨线程数据传递方式 - 暂停功能不要用忙等循环实现,
QWaitCondition的wait方法会让线程进入内核态阻塞,不占用CPU资源 - 线程对象析构前必须确保已经退出运行状态,否则会出现崩溃问题
内容的提问来源于stack exchange,提问作者Syed Iftekharuddin
相关产品推荐
相关产品推荐

