You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.22 11:06:08