首页 / 资讯中心 / 文章详情

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

【QT】系统相关:多线程QThread基础 ★ FEATURED ARTICLE
个人主页艾莉丝努力练剑❄专栏传送门《C语言》《数据结构与算法》《C/C干货分享学习过程记录》《Linux操作系统编程详解》《笔试/面试常见算法从基础到进阶》《Python干货分享》⭐️为天地立心为生民立命为往圣继绝学为万世开太平 艾莉丝的简介文章目录1 ~ Qt 多线程 QThread 基础总览1.1 Qt 多线程与系统线程的关系1.2 QThread 设计思路继承重写 run ()1.2.1 线程入口函数1.3 QThread 核心 API2 ~ 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 Affinity4.2 QThread 两种主流用法4.3 线程退出注意事项4.4 信号槽连接类型结尾1 ~ Qt 多线程 QThread 基础总览1.1 Qt 多线程与系统线程的关系Qt 多线程底层本质仍是操作系统原生线程Linux 下对应 pthreadWindows 对应系统线程 APILinux 线程的原理、线程安全约束全部适用于 Qt 多线程。Qt 对不同操作系统平台原生线程 API 做跨平台封装实现一套代码多平台编译运行。并发技术对比LinuxpthreadC 语言原生库API 繁琐大量使用void*通用指针内存管理复杂实际业务开发很少直接使用。C11std::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 核心 APIAPI功能说明virtual void run()线程入口虚函数子类重写线程业务逻辑编写位置禁止手动调用void start(Priority InheritPriority)向操作系统发起创建线程请求线程已运行时调用无效果线程就绪后自动执行run()static QThread* currentThread()静态方法返回当前正在执行代码所属线程的QThread对象指针bool isRunning() const判断内核线程是否处于运行状态run()未执行完毕返回 truesleep(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 项目实现步骤创建 Qt Widget 项目UI 界面放置QLCDNumber数字显示控件。创建 C 类Thread继承QThread启用Q_OBJECT宏。子类重写run()作为线程入口定义自定义信号用于向主线程通知计时事件。Widget 主线程创建 Thread 对象connect 绑定信号槽调用start()启动子线程。子线程 run 循环计时休眠通过 emit 发射信号主线程槽函数接收信号更新 LCD 显示。2.2.1 thread.h线程子类头文件#ifndefTHREAD_H#defineTHREAD_H#includeQThreadclassThread:publicQThread{Q_OBJECTpublic:explicitThread();protected://重写线程入口函数runprotected虚函数voidrun()override;signals://自定义信号无需实现MOC工具自动生成实现voidnotify();};#endif// THREAD_H2.2.2 thread.cpp线程子类实现#includethread.hThread::Thread(){}voidThread::run(){//子线程执行逻辑禁止操作UI控件for(inti0;i10;i){//休眠1秒阻塞当前子线程不会影响GUI主线程sleep(1);//发射自定义信号通知主线程更新界面emitnotify();}}2.2.3 widget.h主窗口头文件#ifndefWIDGET_H#defineWIDGET_H#includeQWidget#includethread.hQT_BEGIN_NAMESPACEnamespaceUi{classWidget;}QT_END_NAMESPACEclassWidget:publicQWidget{Q_OBJECTpublic:Widget(QWidget*parentnullptr);~Widget();privateslots://槽函数接收子线程信号更新UIvoidhandle();private:Ui::Widget*ui;Thread thread;//子线程对象};#endif// WIDGET_H2.2.4 widget.cpp主窗口实现#includewidget.h#includeui_widget.hWidget::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控件intvalueui-lcdNumber-intValue();value--;ui-lcdNumber-display(value);}2.3 程序时序流程Widget 构造函数执行thread.start()调用操作系统创建子线程。子线程调度执行Thread::run()循环执行sleep(1)每 1 秒emit notify()发射信号。信号跨线程投递放入主线程事件队列。主线程事件循环取出事件执行handle()槽函数更新 LCD 数字。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 典型业务场景网络大文件上传下载、网络请求等待响应。IO大批量磁盘文件读写、加载大型资源文件。后台计算耗时数据处理不允许卡住界面。4 ~ 重要补充知识点4.1 对象依附线程Thread AffinityQThread 对象本身属于创建它的线程不是子线程只有run()内部执行代码运行在内核子线程。继承 QThread 方式中不要给 QThread 子类增加成员对象在 run 外部做信号槽绑定成员对象会依附主线程run()子线程中操作会产生跨线程访问风险。4.2 QThread 两种主流用法继承重写run()本笔记示例用法适合业务逻辑完整、循环常驻运行的任务。QObject moveToThread()不继承 QThread将工作对象移动到 QThread适合使用信号槽驱动、存在事件循环的任务Qt 官方更推荐。4.3 线程退出注意事项禁止调用terminate()强制杀死线程资源泄漏风险。循环任务线程推荐设置原子 bool 标记位控制 run 循环正常退出外部调用wait()等待线程安全结束。对象析构前务必保证线程已经结束避免对象销毁线程还在访问成员造成崩溃。4.4 信号槽连接类型Qt::AutoConnection默认发送与接收同一线程直接调用跨线程自动变为Qt::QueuedConnection投递到接收线程事件循环执行。Qt::QueuedConnection信号发送线程立即返回槽函数在接收者线程事件循环执行跨线程安全。Qt::DirectConnection槽函数直接在信号发射线程执行跨线程使用会引发线程安全问题。结尾uu们本文的内容到这里就全部结束了艾莉丝在这里再次感谢您的阅读艾莉丝努力练剑C/C Linux 底层探索者 | 一个正在努力练剑的技术博主【关注】跟随我一起深耕技术领域见证每一次成长。❤️【点赞】让优质内容被更多人看见让知识传递更有力量。⭐【收藏】把核心知识点存好在需要时随时查、随时用。【评论】分享你的经验或疑问评论区一起交流避坑不要忘记给博主“一键四连”哦“今日练剑达成”“技术之路难免有困惑但同行的人会让前进更有方向。”结语希望对学习Linux相关内容的uu有所帮助不要忘记给博主“一键四连”哦往期回顾【QT】系统相关QT文件博主在这里放了一只小狗大家看完了摸摸小狗放松一下吧૮₍ ˶ ˊ ᴥ ˋ˶₎ა
阅读完成 · 觉得有帮助?
咨询建站