拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

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

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


🎬 个人主页:艾莉丝努力练剑

❄专栏传送门:《C语言》《数据结构与算法》《C/C++干货分享&学习过程记录》
《Linux操作系统编程详解》《笔试/面试常见算法:从基础到进阶》《Python干货分享》
⭐️为天地立心,为生民立命,为往圣继绝学,为万世开太平

🎬 艾莉丝的简介:


文章目录

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


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

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

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

#ifndefTHREAD_H#defineTHREAD_H#include<QThread>classThread:publicQThread{Q_OBJECTpublic:explicitThread();protected://重写线程入口函数run,protected虚函数voidrun()override;signals://自定义信号,无需实现,MOC工具自动生成实现voidnotify();};#endif// THREAD_H

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

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

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

#ifndefWIDGET_H#defineWIDGET_H#include<QWidget>#include"thread.h"QT_BEGIN_NAMESPACEnamespaceUi{classWidget;}QT_END_NAMESPACEclassWidget:publicQWidget{Q_OBJECTpublic:Widget(QWidget*parent=nullptr);~Widget();privateslots://槽函数:接收子线程信号,更新UIvoidhandle();private:Ui::Widget*ui;Thread thread;//子线程对象};#endif// WIDGET_H

2.2.4 widget.cpp(主窗口实现)

#include"widget.h"#include"ui_widget.h"Widget::Widget(QWidget*parent):QWidget(parent),ui(newUi::Widget){ui->setupUi(this);//绑定子线程notify信号到主线程handle槽函数//跨线程自动使用QueuedConnectionconnect(&thread,&Thread::notify,this,&Widget::handle);//启动子线程,调用操作系统API创建内核线程,自动执行run()thread.start();}Widget::~Widget(){//析构前确保线程执行完毕,防止对象销毁线程还在运行thread.wait();deleteui;}voidWidget::handle(){//槽函数运行在主线程,可以安全操作UI控件intvalue=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文件

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

返回列表