文章目录

  • 加载中...

C++

new

编程分享
Qt多线程基础应用方式
发布日期:2025-10-23 08:06:17

一、核心问题与优化方向

 QTimer 

  1. 主线程阻塞:QTimer 任务运行在主线程,高频执行会占用主线程资源,导致界面无响应(如按钮点击、滚动反馈停滞)。
  2. 内存消耗过高:重复创建 QTimer 实例或高频触发任务,易造成内存泄漏或资源浪费。

 

优化方案 多线程架构  线程通信机制  线程池

二、单线程架构的局限性

  • 计算密集型任务阻塞界面:例如传输大型文件时,数据读取、协议解析等操作会持续占用主线程,导致 “界面冻结”(用户操作无反馈)。
  • 任务优先级无法区分:高频次要任务(如定期日志打印)可能抢占核心功能(如用户输入响应)的资源,降低整体程序性能。

解决思路

三、Qt 框架下的多线程实现

 QThread2 

  1. 自定义线程类:继承 QThread,重写保护成员函数 run()(线程任务的核心逻辑在此实现)。
  2. 区分线程角色:主线程:程序启动时默认创建,负责界面渲染、用户交互(如按钮点击、输入处理)。工作线程:由主线程创建,执行耗时 / 高频任务(如数据计算、网络请求)。
  3. 线程启动与终止:调用 start() 启动线程(内部自动调用 run(),进入线程事件循环)。调用 exit()/quit() 结束事件循环,或 terminate() 强制终止线程(需配合 wait() 确保线程安全退出)。

四、QThread 类核心接口解析

QThread  QObject 

4.1 公共函数(线程状态查询与控制)

函数声明 功能说明
bool isFinished() 判断线程是否已结束(返回 true 表示结束)
bool isRunning() 判断线程是否正在运行(返回 true 表示运行中)
Priority priority() 获取线程优先级(如 QThread::HighPriorityQThread::NormalPriority
void setPriority(Priority priority) 设置线程优先级(影响操作系统调度顺序)
void exit(int returnCode=0) 退出线程事件循环,returnCode=0 表示成功,非 0 表示异常
bool wait(unsigned long time) 阻塞当前线程,等待目标线程结束(time 为超时时间,单位:毫秒;time=ULONG_MAX 表示无限等待)

4.2 公共槽函数(线程启动与终止)

槽函数声明 功能说明
void quit() 等效于 exit(0),退出线程事件循环(正常退出)
void start(Priority priority) 启动线程,操作系统根据 priority 调度;内部自动调用 run()
void terminate() 强制终止线程(非安全方式,仅紧急场景使用);调用后需配合 wait() 确保线程释放资源

4.3 信号(线程状态通知)

信号声明 触发时机
void finished() 线程即将结束时发射(可用于主线程释放资源)
void started() 线程开始执行前发射(run() 调用前,可用于初始化)

4.4 静态成员(线程通用操作)

静态函数声明 功能说明
int idealThreadCount() 返回系统理想线程数(通常等于 CPU 核心数,用于线程池优化)
void msleep(unsigned long msecs) 强制当前线程休眠 msecs 毫秒(阻塞线程执行)
void sleep(unsigned long secs) 强制当前线程休眠 secs 秒
void usleep(unsigned long usecs) 强制当前线程休眠 usecs 微秒

4.5 保护函数(线程核心逻辑)

保护函数声明 功能说明
virtual void run() 线程任务核心函数,start() 会自动调用;需重写以实现自定义任务
int exec() 进入线程事件循环(通常在 run() 中调用),等待 exit() 触发退出

五、抽奖功能多线程实例

5.1 自定义抽奖线程类(LotteryThread)

#include <QThread>
#include <QRandomGenerator>
#include <QTime>
#include <QStringList>

class LotteryThread : public QThread {
    Q_OBJECT  // 必须添加,支持信号槽机制

public:
    explicit LotteryThread(QObject *parent = nullptr) : QThread(parent) {}

    // 线程控制接口(供主线程调用)
    void LotteryBegin() { m_Paused = false; }  // 开始抽奖(取消暂停)
    void LotteryPause() { m_Paused = true; }   // 暂停抽奖
    void IsStopThread() { m_stop = true; }     // 标记线程停止

signals:
    // 抽奖结果信号(发送给主线程,参数:次数序号、抽奖结果)
    void prizeResult(int seq, const QString &result);

protected:
    // 重写 run(),实现抽奖核心逻辑
    void run() override {
        m_stop = false;    // 初始化停止标记
        m_seq = 0;         // 初始化次数序号
        qsrand(QTime::currentTime().msec());  // 初始化随机数种子(线程安全)

        // 线程循环:未标记停止则持续运行
        while (!m_stop) {
            if (!m_Paused) {  // 未暂停时,生成抽奖结果
                // 从奖品列表中随机选择一个结果
                QString temp = prizes[qrand() % prizes.size()];
                m_seq++;  // 次数序号自增
                emit prizeResult(m_seq, temp);  // 发送结果给主线程
            }
            msleep(500);  // 线程休眠 500ms,控制抽奖频率(避免高频占用资源)
        }
        quit();  // 退出线程事件循环(等效于 exit(0),正常结束)
    }

private:
    int m_seq = 0;                          // 抽奖次数序号
    const QStringList prizes = {"one", "two", "three", "thanks"};  // 奖品列表
    bool m_Paused = true;                   // 暂停标记(默认暂停)
    bool m_stop = false;                    // 停止标记(默认不停止)
};

5.2 主线程界面类(Dialog)

5.2.1 类定义

#include <QDialog>
#include "LotteryThread.h"  // 包含自定义线程类头文件

namespace Ui {
class Dialog;
}

class Dialog : public QDialog
{
    Q_OBJECT

public:
    Dialog(QWidget *parent = nullptr);
    ~Dialog();

private slots:
    // 接收工作线程的抽奖结果(更新界面)
    void onthreadA(int seq, const QString& Value);
    // 按钮点击槽函数
    void on_pushButton_clicked();    // “开始线程”按钮
    void on_pushButton_4_clicked();  // “结束线程”按钮
    void on_pushButton_2_clicked();  // “开始抽奖”按钮(取消暂停)
    void on_pushButton_3_clicked();  // “暂停抽奖”按钮

protected:
    // 重写关闭事件,确保线程安全退出
    void closeEvent(QCloseEvent* event);

private:
    Ui::Dialog *ui;               // 界面组件对象
    LotteryThread* threadA;       // 抽奖工作线程实例
};

5.2.2 类实现

#include "Dialog.h"
#include "ui_Dialog.h"
#include <QDebug>

Dialog::Dialog(QWidget *parent)
    : QDialog(parent)
    , ui(new Ui::Dialog)
{
    ui->setupUi(this);
    // 创建抽奖线程实例(父对象为当前界面,避免内存泄漏)
    threadA = new LotteryThread(this);
    // 绑定线程信号与界面槽函数:线程发送的抽奖结果 → 界面更新
    connect(threadA, &LotteryThread::prizeResult, this, &Dialog::onthreadA);
}

Dialog::~Dialog()
{
    delete ui;  // 释放界面组件
}

// 接收抽奖结果,更新界面文本框
void Dialog::onthreadA(int seq, const QString &Value)
{
    // 格式化结果文本(序号+结果)
    QString text = QString::asprintf("%d:", seq) + Value;
    ui->textEdit->append(text);  // 在文本框中追加结果
}

// “开始线程”按钮点击:启动工作线程
void Dialog::on_pushButton_clicked()
{
    if (!threadA->isRunning()) {  // 避免重复启动线程
        threadA->start();         // 启动线程(内部调用 run())
        qDebug() << "线程已启动";
    }
}

// “结束线程”按钮点击:安全终止线程
void Dialog::on_pushButton_4_clicked()
{
    if (threadA->isRunning()) {   // 线程运行中才执行终止
        threadA->IsStopThread();  // 标记线程停止
        threadA->wait();          // 等待线程结束(释放资源)
        qDebug() << "线程已结束";
    }
}

// “开始抽奖”按钮点击:取消线程暂停(开始生成结果)
void Dialog::on_pushButton_2_clicked()
{
    threadA->LotteryBegin();
    qDebug() << "抽奖开始";
}

// “暂停抽奖”按钮点击:暂停线程(停止生成结果)
void Dialog::on_pushButton_3_clicked()
{
    threadA->LotteryPause();
    qDebug() << "抽奖暂停";
}

// 界面关闭事件:确保线程安全退出
void Dialog::closeEvent(QCloseEvent *event)
{
    if (threadA->isRunning()) {   // 关闭界面时,若线程仍在运行
        threadA->IsStopThread();  // 标记停止
        threadA->wait();          // 等待线程结束
    }
    event->accept();  // 允许界面关闭
}

六、多线程编程关键注意事项

  • 线程安全:避免多线程直接操作同一资源(如界面组件),需通过 信号槽 实现线程间通信(Qt 信号槽自动处理线程间数据同步)。线程内使用全局变量 / 静态变量时,需加锁(如 QMutex),防止数据竞争。
  • 线程生命周期管理:避免直接调用 terminate() 强制终止线程(可能导致资源泄漏),优先通过 “标记停止 +wait()” 的方式安全退出。界面关闭时,需检查线程状态,确保所有工作线程结束后再释放资源。资源控制:线程内通过 msleep()/usleep() 控制任务频率,避免高频循环占用过多 CPU 资源。大量短期任务建议使用 QThreadPool(线程池),避免频繁创建 / 销毁线程的开销。
  • 信号槽绑定:跨线程绑定信号槽时,默认使用 Qt::QueuedConnection(队列连接),确保主线程安全处理界面更新(避免直接在工作线程操作 UI)。