【QT】系统相关:多线程QThread基础

🎬 个人主页 :艾莉丝努力练剑
❄专栏传送门 :《C语言》《数据结构与算法》《C/C++干货分享&学习过程记录》
《Linux操作系统编程详解》《笔试/面试常见算法:从基础到进阶》《Python干货分享》

⭐️为天地立心,为生民立命,为往圣继绝学,为万世开太平


🎬 艾莉丝的简介:


文章目录

  • [1 ~> Qt 多线程 QThread 基础总览](#1 ~> Qt 多线程 QThread 基础总览)
    • [1.1 Qt 多线程与系统线程的关系](#1.1 Qt 多线程与系统线程的关系)
    • [1.2 QThread 设计思路:继承重写 run ()](#1.2 QThread 设计思路:继承重写 run ())
      • [1.2.1 线程入口函数](#1.2.1 线程入口函数)
    • [1.3 QThread 核心 API](#1.3 QThread 核心 API)
  • [2 ~> QThread 继承方式实战:多线程倒计时案例](#2 ~> QThread 继承方式实战:多线程倒计时案例)
    • [2.1 核心约束:Qt GUI 线程规则](#2.1 核心约束:Qt GUI 线程规则)
    • [2.2 项目实现步骤](#2.2 项目实现步骤)
      • [2.2.1 thread.h(线程子类头文件)](#2.2.1 thread.h(线程子类头文件))
      • [2.2.2 thread.cpp(线程子类实现)](#2.2.2 thread.cpp(线程子类实现))
      • [2.2.3 widget.h(主窗口头文件)](#2.2.3 widget.h(主窗口头文件))
      • [2.2.4 widget.cpp(主窗口实现)](#2.2.4 widget.cpp(主窗口实现))
    • [2.3 程序时序流程](#2.3 程序时序流程)
  • [3 ~> Qt 客户端多线程应用场景](#3 ~> Qt 客户端多线程应用场景)
    • [3.1 服务器多线程 vs Qt 客户端多线程目标差异](#3.1 服务器多线程 vs Qt 客户端多线程目标差异)
    • [3.2 客户端阻塞根源](#3.2 客户端阻塞根源)
    • [3.3 正确处理方案](#3.3 正确处理方案)
    • [3.4 典型业务场景](#3.4 典型业务场景)
  • [4 ~> 重要补充知识点](#4 ~> 重要补充知识点)
    • [4.1 对象依附线程(Thread Affinity)](#4.1 对象依附线程(Thread Affinity))
    • [4.2 QThread 两种主流用法](#4.2 QThread 两种主流用法)
    • [4.3 线程退出注意事项](#4.3 线程退出注意事项)
    • [4.4 信号槽连接类型](#4.4 信号槽连接类型)
  • 结尾


1 ~> Qt 多线程 QThread 基础总览

1.1 Qt 多线程与系统线程的关系

  • Qt 多线程底层本质仍是操作系统原生线程,Linux 下对应 pthread,Windows 对应系统线程 API,Linux 线程的原理、线程安全约束全部适用于 Qt 多线程。
  • Qt 对不同操作系统平台原生线程 API 做跨平台封装,实现一套代码多平台编译运行。
  • 并发技术对比
    • Linux pthread:C 语言原生库,API 繁琐,大量使用void*通用指针,内存管理复杂,实际业务开发很少直接使用。
    • C++11 std::thread:对系统线程封装,支持 lambda、可调用对象,具备类型检查,规避void*,C++ 开发中使用广泛;底层依然封装操作系统线程。
    • Qt QThread:面向对象封装,提供事件循环、信号槽线程间通信机制,更适配 Qt GUI 客户端开发。

**注意:**掌握操作系统原生线程原理,是理解上层std::thread、QThread的前置基础。

1.2 QThread 设计思路:继承重写 run ()

1.2.1 线程入口函数

  • 线程的核心:指定线程入口函数,新线程被操作系统调度后执行入口函数内业务逻辑。
  • std::thread/pthread:传入函数指针、lambda 表达式作为入口。
  • QThread 传统用法:继承QThread子类,重写虚函数 **run()**,利用 C++ 多态实现线程入口。
    • run()不会由开发者手动调用;调用QThread::start()后,操作系统创建内核线程,自动调用子类重写的run()。
    • 多态会带来虚函数表运行时查找的微小开销;对于游戏引擎、高性能服务器追求极致性能场景会规避;Qt 客户端 GUI 开发对该开销不敏感,属于常规开发模式。

1.3 QThread 核心 API

API 功能说明
virtual void run() 线程入口虚函数,子类重写,线程业务逻辑编写位置,禁止手动调用
void start(Priority = InheritPriority) 向操作系统发起创建线程请求;线程已运行时调用无效果;线程就绪后自动执行run()
static QThread* currentThread() 静态方法,返回当前正在执行代码所属线程的QThread对象指针
bool isRunning() const 判断内核线程是否处于运行状态,run()未执行完毕返回 true
sleep(unsigned long secs) 线程休眠,单位秒;仅作用于调用该函数的线程
msleep(unsigned long msecs) 线程休眠,单位毫秒
usleep(unsigned long usecs) 线程休眠,单位微秒
bool wait(unsigned long time = ULONG_MAX) 阻塞等待线程结束,等价 Linuxpthread_join();等待线程执行完毕或者超时返回;线程未启动直接返回 true;超时返回 false;永久阻塞使用默认参数
void terminate() 强制终止线程,操作系统调度时机不确定,资源无法安全释放,业务开发禁止使用
void finished()信号 线程执行完毕发射该信号,可绑定槽做线程资源清理工作

重点: sleep/msleep/usleep是线程休眠,会阻塞当前调用线程;GUI 主线程禁止调用休眠,会冻结界面事件循环。


2 ~> QThread 继承方式实战:多线程倒计时案例

2.1 核心约束:Qt GUI 线程规则

  • 所有控件、界面组件只能在主线程(GUI 线程)操作修改,其他子线程禁止直接读写 UI 控件,会引发程序崩溃。
  • 子线程只负责计算、计时、IO 处理;子线程通过自定义信号发送数据,信号槽跨线程通知主线程,主线程槽函数内部更新界面。
  • 跨线程信号槽默认自动使用Qt::QueuedConnection队列连接,事件循环分发到接收者所属线程执行槽函数。

2.2 项目实现步骤

  1. 创建 Qt Widget 项目,UI 界面放置QLCDNumber数字显示控件。
  2. 创建 C++ 类Thread,继承QThread,启用Q_OBJECT宏。
  3. 子类重写run()作为线程入口;定义自定义信号用于向主线程通知计时事件。
  4. Widget 主线程创建 Thread 对象;connect 绑定信号槽;调用start()启动子线程。
  5. 子线程 run 循环计时休眠,通过 emit 发射信号;主线程槽函数接收信号更新 LCD 显示。

2.2.1 thread.h(线程子类头文件)

cpp 复制代码
#ifndef THREAD_H
#define THREAD_H

#include <QThread>

class Thread : public QThread
{
    Q_OBJECT
public:
    explicit Thread();

protected:
    //重写线程入口函数run,protected虚函数
    void run() override;

signals:
    //自定义信号,无需实现,MOC工具自动生成实现
    void notify();
};

#endif // THREAD_H

2.2.2 thread.cpp(线程子类实现)

cpp 复制代码
#include "thread.h"

Thread::Thread()
{

}

void Thread::run()
{
    //子线程执行逻辑,禁止操作UI控件
    for(int i = 0; i < 10; ++i)
    {
        //休眠1秒,阻塞当前子线程,不会影响GUI主线程
        sleep(1);
        //发射自定义信号,通知主线程更新界面
        emit notify();
    }
}

2.2.3 widget.h(主窗口头文件)

cpp 复制代码
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include "thread.h"

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT
public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

private slots:
    //槽函数:接收子线程信号,更新UI
    void handle();

private:
    Ui::Widget *ui;
    Thread thread; //子线程对象
};

#endif // WIDGET_H

2.2.4 widget.cpp(主窗口实现)

cpp 复制代码
#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    //绑定子线程notify信号到主线程handle槽函数
    //跨线程自动使用QueuedConnection
    connect(&thread, &Thread::notify, this, &Widget::handle);

    //启动子线程,调用操作系统API创建内核线程,自动执行run()
    thread.start();
}

Widget::~Widget()
{
    //析构前确保线程执行完毕,防止对象销毁线程还在运行
    thread.wait();
    delete ui;
}

void Widget::handle()
{
    //槽函数运行在主线程,可以安全操作UI控件
    int value = ui->lcdNumber->intValue();
    value--;
    ui->lcdNumber->display(value);
}

2.3 程序时序流程

  1. Widget 构造函数执行,thread.start()调用,操作系统创建子线程。
  2. 子线程调度执行Thread::run(),循环执行sleep(1),每 1 秒emit notify()发射信号。
  3. 信号跨线程投递,放入主线程事件队列。
  4. 主线程事件循环取出事件,执行handle()槽函数,更新 LCD 数字。
  5. for 循环执行 10 次结束,run()函数 return;线程结束,发射finished()信号。

3 ~> Qt 客户端多线程应用场景

3.1 服务器多线程 vs Qt 客户端多线程目标差异

  • 服务器端多线程:主要目标利用 CPU 多核,并行计算,充分压榨 CPU 算力,提升任务处理速度。
  • Qt 客户端 GUI 程序多线程核心目标:解阻塞,保护主线程事件循环,避免界面卡死。

3.2 客户端阻塞根源

GUI 主线程职责:运行事件循环,处理鼠标、键盘、窗口绘制全部用户交互。

  • 如果主线程执行耗时 IO 操作(网络大文件上传下载、大量磁盘读写、资源加载),主线程被系统阻塞挂起,事件循环停止。
  • 现象:窗口无响应,操作系统弹出程序未响应提示,用户无法操作界面。

3.3 正确处理方案

  • 将耗时 IO、等待类业务放到独立子线程执行;阻塞只会发生子线程。
  • 主线程持续运行事件循环,持续响应用户操作。
  • 子线程计算 / IO 完成,通过信号槽把结果回传给主线程更新界面。

3.4 典型业务场景

  1. 网络:大文件上传下载、网络请求等待响应。
  2. IO:大批量磁盘文件读写、加载大型资源文件。
  3. 后台计算:耗时数据处理,不允许卡住界面。

4 ~> 重要补充知识点

4.1 对象依附线程(Thread Affinity)

  • QThread 对象本身属于创建它的线程,不是子线程;只有run()内部执行代码运行在内核子线程。
  • 继承 QThread 方式中,不要给 QThread 子类增加成员对象,在 run 外部做信号槽绑定 ,成员对象会依附主线程,run()子线程中操作会产生跨线程访问风险。

4.2 QThread 两种主流用法

  1. 继承重写run()(本笔记示例用法),适合业务逻辑完整、循环常驻运行的任务。
  2. QObject + moveToThread():不继承 QThread,将工作对象移动到 QThread,适合使用信号槽驱动、存在事件循环的任务(Qt 官方更推荐)。

4.3 线程退出注意事项

  1. 禁止调用terminate()强制杀死线程,资源泄漏风险。
  2. 循环任务线程,推荐设置原子 bool 标记位,控制 run 循环正常退出,外部调用wait()等待线程安全结束。
  3. 对象析构前,务必保证线程已经结束,避免对象销毁,线程还在访问成员造成崩溃。

4.4 信号槽连接类型

  • Qt::AutoConnection默认:发送与接收同一线程直接调用;跨线程自动变为Qt::QueuedConnection,投递到接收线程事件循环执行。
  • Qt::QueuedConnection:信号发送线程立即返回,槽函数在接收者线程事件循环执行,跨线程安全。
  • Qt::DirectConnection:槽函数直接在信号发射线程执行,跨线程使用会引发线程安全问题。

结尾

uu们,本文的内容到这里就全部结束了,艾莉丝在这里再次感谢您的阅读!

|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ### 艾莉丝努力练剑 C/C++ & Linux 底层探索者 | 一个正在努力练剑的技术博主 *** ** * ** *** 👀 【关注】 跟随我一起深耕技术领域,见证每一次成长。 ❤️ 【点赞】 让优质内容被更多人看见,让知识传递更有力量。 ⭐ 【收藏】 把核心知识点存好,在需要时随时查、随时用。 💬 【评论】 分享你的经验或疑问,评论区一起交流避坑! 不要忘记给博主"一键四连"哦! "今日练剑达成!" "技术之路难免有困惑,但同行的人会让前进更有方向。" |

结语:希望对学习Linux相关内容的uu有所帮助,不要忘记给博主"一键四连"哦!

往期回顾:

【QT】系统相关:QT文件

🗡博主在这里放了一只小狗,大家看完了摸摸小狗放松一下吧!🗡 ૮₍ ˶ ˊ ᴥ ˋ˶₎ა

相关推荐
sunshine22 girl43 分钟前
Java学习五 面向对象高级5 内部类4-匿名内部类(重点)
java·学习
l1t1 小时前
DeepSeek总结的PIVCO-Huffman编码性能优化
数据结构·c++·算法
ysu_03141 小时前
uv实战指南-从Python版本虚拟环境到pyproject依赖管理
开发语言·python·pip·uv·虚拟环境·包管理·依赖管理
妄汐霜1 小时前
SSE,RocketMQ,MQTT不同之处
笔记·学习·rocketmq
starsky762381 小时前
Agent学习——AI Agent总体概览
学习
王维同学1 小时前
ETW监控-KswordARK功能展示
c++·windows·安全
東隅已逝,桑榆非晚1 小时前
c++模板进阶
开发语言·c++·笔记·学习
JWASX1 小时前
Java 转 go 学习 - map
学习·golang
Cc.Y6 小时前
Java零基础入门:字符串深度掌握——从基础API到StringBuilder性能优化
java·开发语言·性能优化