【QT】多线程相关教程

一、核心概念与 Qt 线程模型

1.线程与进程的区别:
线程是程序执行的最小单元,进程是资源分配的最小单元,线程共享进程的内存空间(堆,全局变量等),而进程拥有独立的内存空间。Qt线程只要关注同一进程内的并发。

2.为什么使用多线程
当程序中有多个耗时的操作时候,为了提高性能,防止GUI线程阻塞,可以处理耗时操作

3.QThread 类的相关接口
start():启动线程,调用run方法
run():线程的入口点,子类化 QThread 并重写此方法是一种使用线程的方式(传统方式)。线程在此函数中执行。
quit() / exit(int returnCode): 请求线程退出事件循环(如果正在运行)。
wait([unsigned long time = ULONG_MAX]): 阻塞调用线程,直到目标线程结束执行或超时。
isRunning() / isFinished(): 查询线程状态。
finished 信号:线程执行完毕(run() 返回)时发射。重要: 连接此信号进行资源清理(如 deleteLater)。
started 信号:线程启动后(run() 执行前)发射。

4.事件循环 (Event Loop)
核心概念: 每个线程都可以拥有自己的事件循环(由 QEventLoop 管理)。主线程(GUI 线程)默认运行事件循环。
作用: 处理事件(如定时器事件、网络事件、投递的事件、队列连接的信号槽调用)。
QThread::exec(): 进入事件循环(在 run() 方法中调用)。
Worker 对象 + moveToThread + 事件循环模式: 这是现代 Qt 多线程编程的主流模式。对象被移动到新线程后,它的槽函数将在新线程的事件循环中被调用。

6.信号与槽 (Signals & Slots) 的连接类型
Qt 的信号槽机制是线程安全的。
连接类型 (Connection Type) 决定槽函数在哪个线程执行:
Qt::AutoConnection (默认): 如果发送者和接收者在同一线程,行为同 DirectConnection;否则,行为同 QueuedConnection。
Qt::DirectConnection: 槽函数在发送者所在线程中立即执行(就像直接函数调用)。
Qt::QueuedConnection: 槽函数的调用被转换为一个事件,放入接收者所在线程的事件队列。接收者线程的事件循环稍后会从队列中取出并执行该槽函数。这是跨线程通信最安全、最常用的方式!
Qt::BlockingQueuedConnection: 类似 QueuedConnection,但发送者线程会阻塞,直到接收者线程的槽函数执行完毕。慎用!容易死锁。 确保接收者线程能及时处理事件。
Qt::UniqueConnection: 可以与上述类型组合使用 (AutoConnection | UniqueConnection),确保相同的信号和槽之间只有一个连接。

跨线程信号槽参数传递: 参数类型必须是 Qt 元对象系统已知的类型( 使用qRegisterMetaType() 注册自定义类型)。对于 QueuedConnection 和 BlockingQueuedConnection,参数会被复制传递。

二、创建和管理线程

1.使用 QThread 的两种主要模式:
使用 QThread 的两种主要模式:
1.1 子类化 QThread (传统方式):
(1)继承 QThread。
(2)重写 run() 方法,将需要在线程中执行的代码放入其中。
(3)创建子类实例,调用 start()。
局限: run() 是唯一入口,难以处理多个任务或利用事件循环。对象本身(this)仍留在原线程(通常是主线程)。
在QThread子类的构造函数中创建的对象,其线程亲和性是创建该QThread对象的线程(通常是主线程),而不是新线程。这会导致在该对象上使用定时器、信号槽等出现问题。正确的方式是在run()函数中创建这些对象,这样它们的线程亲和性才是新线程。但这样又使得对象创建在run()函数内部,难以从外部管理。线程结束时要清理run()中创建的对象,需要自己管理,容易出错。

1.2 Worker 对象 + moveToThread (推荐方式):
(1)创建一个普通的 QObject 子类(Worker 对象),它包含需要通过槽函数执行的任务。
(2)创建一个 QThread 实例。
(3)创建 Worker 对象实例(此时它在创建者线程,通常是主线程)。
(4)调用 workerObject->moveToThread(workerThread)。关键步骤!
(5)连接 Worker 对象的信号和槽(通常使用 QueuedConnection,但 AutoConnection 在 moveToThread 后通常也能正确处理)。
(6)启动线程 workerThread->start()。这会启动线程的事件循环。
(7)通过信号触发 Worker 对象的槽函数执行任务。任务将在新线程中执行。
(8).请求线程退出:workerThread->quit() 或 workerThread->requestInterruption()(更安全)。
(9)连接 workerThread->finished() 信号到 workerThread->deleteLater() 和 workerObject->deleteLater() 进行自动清理。

//主线程中创建:QThread 实例和worker 对象
Worker* worker = new Worker(); 
QThread* thread = new QThread(this);
worker->moveToThread(thread );
//现在worker属于新线程
thread->start(); // 内部调用exec()启动事件循环
//通过信号槽提交任务
QObject::connect(this, &Controller::startTask, worker, &Worker::doWork);
// 线程结束时自动清理,不然手动在析构函数中delect worker也可以
connect(thread, &QThread::finished, worker, &QObject::deleteLater);
/*因为worker有指定父对象所有这儿不用删除了
connect(thread, &QThread::finished, thread, &QObject::deleteLater); */// 在子线程执行耗时操作    
void Worker::doWork() {............
}      

在这里插入图片描述
关键原则:谁创建谁删除,跨线程对象使用 deleteLater

在这里插入图片描述

新旧方式的对比
在这里插入图片描述

在 Worker 对象中不要做:
创建 QWidget 或其子类(GUI 对象必须在 GUI 线程创建)。
直接操作 GUI(通过信号通知 GUI 线程更新)。
优点: 更符合 Qt 对象模型,可以利用事件循环处理多个任务(定时器、网络等),更灵活,资源管理更清晰。

2.线程池 (QThreadPool 和 QtConcurrent)
QThreadPool: 管理一组可重用的线程。用于执行 QRunnable 任务。
QRunnable: 定义需要执行的任务(重写 run() 方法)。
QThreadPool::globalInstance(): 获取全局线程池实例。
QThreadPool::start(QRunnable *task, int priority = 0): 提交任务到线程池。
优点: 避免频繁创建销毁线程的开销,控制最大并发数。

QtConcurrent 命名空间: 提供高级 API 简化并行计算,底层通常使用 QThreadPool。
run(Function function, …): 在单独线程中运行函数。
map(), mapped(), filtered(), reduce() 等:对容器进行并行操作。
QFuture, QFutureWatcher: 用于监控异步计算的结果和状态。
优点: 代码简洁,易于使用,适合数据并行任务。

三、线程同步与通信

1.互斥锁 QMutex 保护共享数据访问

2.读写锁 (QReadWriteLock, QReadLocker, QWriteLocker) 优化“读多写少”场景。

3.信号量(QSemaphore) 控制对多个相同资源的访问。

4.条件变量
允许线程在特定条件不满足时睡眠等待,并在条件可能改变时被其他线程唤醒。

wait(QMutex *lockedMutex): 原子操作: 释放 lockedMutex 并阻塞等待。被唤醒后,在返回前会重新获取 lockedMutex。

wakeOne(): 唤醒一个等待的线程(任意)。

wakeAll(): 唤醒所有等待的线程。

经典模式: 生产者-消费者。

5.原子操作
对基本数据类型(整数、指针)提供无锁的原子操作(读、写、加减、比较交换等)。

轻量级, 适用于简单的计数器、标志位等场景。不能替代锁保护复杂操作或多变量。
6.跨线程通信的首选:信号与槽
这是 Qt 中最安全、最便捷的跨线程通信机制。利用事件循环传递消息

四、线程安全与最佳实践

1.GUI 线程规则 (黄金法则):
所有用户界面操作(创建、访问、更新 QWidget 及其子类)都必须在主线程(GUI 线程)中进行。

子线程需要更新 UI 时,必须通过信号槽(QueuedConnection)通知主线程进行更新。
2.资源管理
对象树与所有权: Qt 的父子关系管理在跨线程时不适用。父对象和子对象必须在同一线程。

动态对象创建与销毁:

在哪个线程创建对象,该对象通常就“属于”那个线程。

使用 moveToThread() 改变所有权。

安全删除: 使用 obj->deleteLater()。该方法会将删除请求放入对象所在线程的事件队列,由事件循环安全地执行删除操作。这是跨线程删除对象的正确方式! 特别是在连接 QThread::finished() 信号时使用。

3.避免死锁
遵循锁的固定顺序。

最小化临界区(持锁时间)。

谨慎使用嵌套锁。

优先使用 RAII 锁管理 (QMutexLocker 等)。

避免在持锁时等待另一个线程的信号(容易死锁),或使用带超时的等待。
4.退出
请求退出,而非强制终止 (terminate() 非常危险,可能导致资源泄露、状态不一致,应避免使用)。

使用 QThread::requestInterruption() 设置中断请求标志。

在 Worker 对象的耗时操作中定期检查 QThread::isInterruptionRequested(),并在检测到时提前退出。

调用 quit() 或 exit() 请求线程退出事件循环。

使用 wait()(可选,需设置合理超时)确保线程结束。

利用 finished() 信号进行清理 (deleteLater)。
5.异常处理
线程中的异常不会传播到创建该线程的线程(如主线程)。

必须在 run() 或 Worker 对象的槽函数内部捕获并处理所有可能的异常,否则会导致线程崩溃(整个进程通常不会退出,但该线程的工作停止)。
6.性能考量
线程创建销毁有开销,优先考虑线程池 (QThreadPool, QtConcurrent)。

同步原语(锁)有开销,尽量减少竞争(锁粒度、读写锁、原子操作)。

跨线程通信(信号槽 QueuedConnection)有事件投递和复制的开销。

平衡线程数量和任务粒度。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/90808.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/90808.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

VS 版本更新git安全保护问题的解决

问题:我可能移动了一个VS C# 项目,然后,发现里面的git版本检测不能用了 正在打开存储库: X:\Prj_C#\3D fatal: detected dubious ownership in repository at X:/Prj_C#/3DSnapCatch X:/Prj_C#/3D is owned by:S-1-5-32-544 but the current …

Git常用命令一览

Git 是基于 Linux内核开发的版本控制工具。与常用的版本控制工具 CVS, Subversion 等不同,它采用了分布式版本库的方式,不必服务器端软件支持(ps:这得分是用什么样的服务端,使用http协议或者git协议等不太一样。并且在…

基于 JSON 文件定位图片缺陷点并保存

基于JSON的图片缺陷处理流程 ├── 1. 输入检查 │ ├── 验证图片文件是否存在 │ └── 验证JSON文件是否存在 │ ├── 2. 数据加载 │ ├── 打开并加载图片 │ └── 读取并解析JSON文件 │ ├── 3. 缺陷信息提取 │ ├── 检查JSON中是否存在shapes字…

Redis基础学习(五大值数据类型的常用操作命令)

目录 一、Redis基本知识与Redis键(key)常用操作命令。 二、Redis的五大值的数据类型。(value) 三、Redis关于键(key)的值常用操作指令表格统计。 (1)字符串(String&#…

Ubuntu——办公软件 LibreOffice 安装与使用指南

十四、LibreOffice 安装与使用1、核心组件组件​​​​图标​​​​对应MS Office​​​​核心功能定位​​​​Writer​​📝Word专业文档处理与排版​​Calc​​📊Excel数据计算与分析​​Impress​​🎬PowerPoint演示文稿制作​​Draw​​&…

Securecrt丢失tab以及终端重新配色

今天在使用 Securecrt 的时候,发现 Securecrt 的 tab 标签消失不见了,仔细回想起来,应该是上一次误按了 alt enter 最大化,然后导致配置丢失的问题 还有表现就是菜单中的 Session Tabs 无论勾选还是不勾选都没有任何变化&#xf…

frp搭建内网穿透教程

frp搭建内网穿透教程 步骤1:准备工作 公网服务器:需要一台具有公网IP的服务器作为中转服务器,安装frp服务器端(frps)。内网设备:需要暴露服务的内网设备,安装frp客户端(frpc&#xf…

【JavaEE进阶】图书管理系统(未完待续)

目录 用户登录 添加图书 图书列表 修改图书 删除图书 批量删除 拦截器 🍃前言 什么是拦截器? 拦截器的基本使用 自定义拦截器 注册配置拦截器 拦截路径 拦截器执行流程 项目实现统一拦截 定义拦截器 注册配置拦截器 前⾯图书管理系统, 咱们只完成了⽤⼾登录和图书列…

基于同花顺API的熊市与牛市识别模型开发及因子分析

基于同花顺API的熊市与牛市识别模型开发及因子分析 1. 引言 1.1 研究背景与意义 金融市场中的牛市与熊市识别一直是投资者和研究人员关注的重点问题。牛市(Bull Market)通常指价格持续上涨的市场环境,投资者信心充足,交易活跃;而熊市(Bear Market)则指价格持续下跌的市场…

AMD 锐龙 AI MAX+ 395 处理器与端侧 AI 部署的行业实践

2025 年 7 月 10 日,AMD 在深圳召开 Mini AI 工作站行业解决方案峰会,正式发布基于锐龙 AI MAX 395 处理器的端侧 AI 部署方案,与 200 余家生态伙伴共同探讨 AI 技术在千行百业的落地路径。这一硬件平台通过异构计算架构与开放生态设计&#…

期权盘位是什么意思?

本文主要介绍期权盘位是什么意思?“期权盘位”并非金融交易中的标准术语,可能是口语化表达或对某些概念的简化描述。期权盘位是什么意思?1. 期权盘口的“价位”(买卖报价位置)在期权交易中,“盘口”通常指实…

【Trea】Trea国内版|国际版|海外版下载|Mac版|Windows版|Linux下载配置教程

【Trea】Trea国内版|国际版|海外版下载|Mac版|Windows版下载配置教程 本文适用读者: 想要第一次安装 Trea需要在 Windows 或 macOS 上完成环境配置想深入了解 Doubao、DeepSeek、ChatGPT、Claude 等模型在 Trea 中的接…

MyBatis实现分页查询-苍穹外卖笔记

首先分页查询的原理是SQL的limit关键字。LIMIT 子句用于限制 SQL 查询返回的记录数。它接受一个或两个整数参数,第一个参数表示偏移量,第二个参数表示返回的最大记录数。我们完全可以使用前端传给我们的page,pageSize,自己去计算limit的参数,…

系统性能评估方法深度解析:从经典到现代

评估本质:系统性能评估是通过量化分析衡量计算机系统在特定工作负载下的表现能力,核心目标是建立可比较的性能基准,为系统设计、选型和优化提供科学依据。一、评估方法分类体系 #mermaid-svg-0ceD4AA2KDwzwtb6 {font-family:"trebuchet …

WebSocket实现多人实时在线聊天

最近公司在做一个婚恋app&#xff0c;需要增加一个功能&#xff0c;实现多人实时在线聊天。基于WebSocket在Springboot中的使用&#xff0c;前端使用vue开发。 一&#xff1a;后端 1. 引入 websocket 的 maven 依赖 <dependency><groupId>org.springframework.bo…

学习笔记随记-FPGA/硬件加速

一、FPGA&#xff1a;Field Programmable Gate Array 现场可编程门阵列 可编程输入/输出单元、基本可编程逻辑单元、嵌入式块RAM、丰富的布线资源、底层嵌入功能单元和内嵌专用硬核。 可编程输入/输出单元&#xff08;I/O&#xff09;单元 输入/输出&#xff08;Input/Ouput&…

docker宿主机修改ip后起不来问题解决

确保容器已经连接到了正确的网络。如果没有&#xff0c;你可以使用以下命令将容器连接到网络&#xff1a; 1、停止docker网络 ifconfig docker0 down1. 停止 Docker 服务 sudo systemctl stop docker2. 删除 docker0 接口 sudo ip link delete docker03、删除旧的网桥 docker n…

G1 垃圾回收算法详解

目录 简介 G1 GC 的设计目标 内存结构 回收过程 初始标记&#xff08;Initial Mark&#xff09;并发标记&#xff08;Concurrent Mark&#xff09;最终标记&#xff08;Final Mark / Remark&#xff09;筛选回收&#xff08;Cleanup / Evacuation&#xff09; 混合回收&…

JavaEE多线程——锁策略 CAS synchronized优化

目录前言1.锁策略1.1 乐观锁和悲观锁1.2 重量级锁和轻量级锁1.3 挂起等待锁和自旋锁1.4 公平锁和非公平锁1.5 可重入锁和不可重入锁1.6 读写锁2.CAS2.1 CAS的应用2.2 CAS的ABA问题3.synchronized优化3.1锁升级3.2锁消除3.3锁粗化总结前言 本篇文章主要介绍多线程中锁策略、CAS…

Windows符号链接解决vscode和pycharm占用C盘空间太大的问题

Windows符号链接解决vscode和pycharm占用C盘空间太大的问题 参考文章&#xff1a;Windows符号链接 1、找到vscode和pycharm在C盘的缓存文件夹。 C:\Users\用户名\AppData\Roaming\Code C:\Users\用户名\.vscode\extensionsC:\Users\用户名\AppData\Local\JetBrains C:\Users…