笔记:Qt开发之多线程的实现(QThread、moveToThread)

目标:了解Qt实现多线程的两种基本方式(QThread、moveToThread),各自的实现方式、使用场景。

1,子类化QThread

实现方式:继承QThread类,重写run()函数实现多线程

class WorkerThread : public QThread
{
	Q_OBJECT
	
public:
	explicit MyThread(QObject *parent = nullptr) : QObject(parent), m_isStop(false) {}
	void run() {
		QString result;
		while(!m_isStop) {
			qDebug()<<"child thread"<<QThread::currentThreadId()<<endl;
			/* ... here is the expensive or blocking operation ... */
			emit resultReady(result);
			QThread::sleep(5);  
		}
	}
	void stop(){m_isStop = true;}
	
signals:
  void resultReady(const QString &s);
  
private:
	bool m_isStop;//停止标志位  
};

void MyObject::startWorkInAThread()
{
  WorkerThread *workerThread = new WorkerThread(this);
  connect(workerThread, &WorkerThread::resultReady, this, &MyObject::handleResults);
  connect(workerThread, &WorkerThread::finished, workerThread, &QObject::deleteLater);
  workerThread->start();
}

默认情况下,run()通过调用exec()来启动事件循环,并在线程内运行 Qt 事件循环。

必须调用QThread的quit()函数或者exit()函数才可以使子线程退出消息循环,

并且有时还不是马上就退出,需要等到CPU的控制权交给线程的exec()。

所以先要thread.quit();使退出子线程的消息循环, 然后thread.wait();在主线程中回收子线程的资源。

应用场景

适合相对简单独立运行的常驻或后台任务,不需要跨线程频繁通信的任务

注意事项

1,QThread 实例位于实例化它的旧线程中,而不是位于调用 run()的新线程中。

2,run()函数作为子线程的入口,即子线程从run()开始执行。在子线程中完成的工作都要写在run()函数中。

3,Thread(即创建的类)中的成员变量属于主线程,在访问前需要判断访问是否安全。

2,自定义类moveToThread

实现方式:定义QObject派生类,然后将其对象move到QThread中

class Worker : public QObject
{
  Q_OBJECT

public:
    explicit Worker(QObject *parent = nullptr) : QObject(parent), m_isStop(false) {}
	~Worker() { qDebug() << "Worker destroyed in thread" << QThread::currentThreadId(); }
	void stop() { m_isStop = true; }
	
public slots:
  void doWork(const QString &parameter) {
	  QString result;
	  if(!m_isStop) {
		  
		  /* ... here is the expensive or blocking operation ... */
		  emit resultReady(result);		  
	  }
  }

signals:
  void resultReady(const QString &result);
  
private:
    bool m_isStop;
};

class Controller : public QObject
{
  Q_OBJECT
  QThread workerThread;
public:
  Controller() {
	  //不要给对象指定父对象
	  Worker *worker = new Worker;
	  
	  // 将Worker对象移动到新创建的线程中
	  worker->moveToThread(&workerThread);
	  
	  
	  connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
	  connect(this, &Controller::operate, worker, &Worker::doWork);
	  connect(worker, &Worker::resultReady, this, &Controller::handleResults);
	  connect(&workerThread, &QThread::started, worker, &Worker::doWork);
	  workerThread.start();// 开启线程
  }
  ~Controller() {
	  workerThread.quit();// 等待线程结束
	  workerThread.wait();
  }
public slots:
  void handleResults(const QString &);
signals:
  void operate(const QString &);
};

QThread应该被看做是操作系统线程的接口或控制点,而不应该包含需要在新线程中运行的代码。

需要运行的代码应该放到一个QObject的子类中,然后将该子类的对象moveToThread到新线程中。

采用moveToThread的方式,将槽函数所在对象移入子线程,发送信号函数在主线程,槽函数在单独的子线程。

应用场景

适合线程间频繁通信的任务

注意事项

1,自定义类对象不要指定父对象,否则提示:QObject::moveToThread: Cannot move objects with a parent

2,把子线程的finished信号和自定义类对象的deleteLater槽连接,结束子线程则自动析构自定义类的对象。

综上所述

1,常驻任务使用继承QThread重写run,线程逻辑相对独立,不需要与主线程频繁交互。

2,其它的一次性任务或者间歇性任务尽量都用moveToThread,线程执行的任务与 Qt 的事件系统(如信号和槽)交互

允许在不同的线程中运行任务,同时保持与主线程的通信。

3,QThread和connect的关系

信号与槽的连接函数的原型

bool QObject::connect (const QObject * sender, 
                        const char * signal, 
                        const QObject * receiver,
                        const char * method,
                        Qt::ConnectionType type = Qt::AutoConnection)

其中第5个参数决定信号与槽的连接方式,用于决定槽函数被调用时的相关行为。

Qt::AutoConnection              默认连接
Qt::DirectConnection            槽函数立即调用
Qt::BlockingQueuedConnection    同步调用
Qt::QueuedConnection            异步调用
Qt::UniqueConnection            单一连接

(1) Qt::DirectConnection(立即调用)

直接在发送信号的线程中调用槽函数(无论发送信号和槽函数是否位于同一线程),等价于槽函数的实时调用。

也就是说槽函数在发送信号所在线程调用。直接连接,其实就等同于直接调用。

(2) Qt::QueuedConnection(异步调用)

信号发送至目标线程的事件队列(发送信号和槽函数位于不同线程),交由目标线程处理,当前线程继续向下执行。

当信号发送时候,槽函数不会直接调用,直到接受者线程取得控制权时进行事件处理循环时候,槽函数才会调用执行。

跨线程时只有队列连接是安全的,队列连接借助的是事件系统,所以你可以通过postEvent在线程间传递数据。

(3) Qt::BlockingQueuedConnection(同步调用)

信号发送至目标线程的事件队列,由目标线程处理。当前线程阻塞等待槽函数的返回,之后向下执行。

Qt::BlockingQueuedConnection 和QueuedConnectionx相同,但是sender发送后线程会进入阻塞状态,

只有receiver线程执行槽函数完成,才会结束阻塞状态,所以这种参数类型设定情况下,sender和receiver不能在同一个线程,否则会造成死锁发生。

(4) Qt::AutoConnection(默认连接)

当发送信号线程=槽函数线程时,效果等价于Qt::DirectConnection;

当发送信号线程!=槽函数线程时,效果等价于Qt::QueuedConnection。

Qt::AutoConnection是connect()函数第5个参数的默认值,也是实际开发中字常用的连接方式。

(5) Qt::UniqueConnection(单一连接)

功能和AutoConnection相同,同样能自动确定连接类型,但是加了限制:同一个信号和同一个槽函数之间只能有一个连接。

注意:如果槽函数工作在receiver线程(不和sender在同一个线程),并且槽函数中有耗时操作,

比如while循环等,这个时候sender在发送信号,槽函数是不会响应的,除非槽函数工作在sender线程中,也就是要把参数设置为DirectConnection或者槽函数所在线程开启事件循环。

4,多线程开发总结

4.1 确保线程安全

多线程程序需要确保线程安全,特别是在多个线程共享数据时更为重要。

需要使用互斥锁、信号槽等机制来协调线程之间的访问。

同时,需要避免在不同线程中对同一对象进行相互矛盾的操作,以避免出现死锁等问题。

4.2 遵循对象树和内存管理原则

在Qt中,使用多线程时需要遵循对象树和内存管理原则。

通常情况下,QObject对象只能在创建它的线程中使用。

如果需要在其他线程中使用,可以通过使用moveToThread方法将其移动到其他线程中。

同时,需要注意对象的创建和销毁时机,避免出现内存泄漏等问题。

4.3 选择合适的多线程方式

在使用多线程时,需要选择合适的多线程方式。

通常建议使用自定义类并使用moveToThread方法的方式,而不是继承QThread的方式。

这样可以更好地管理线程的生命周期和对象树,同时也更易于实现线程安全和单元测试。

4.4 控制线程数目

在使用多线程时需要控制线程数目,避免过多的线程导致资源浪费和性能下降。

可以通过Qt提供的QThreadPool类来管理线程池,从而更好地控制线程数目。

4.5 避免阻塞主线程

在使用多线程时,需要注意避免阻塞主线程。

长时间的计算或IO操作应该在子线程中完成,以便主线程能够更快地响应用户操作。

同时,也需要注意信号槽的连接方式,避免连接方式不当导致阻塞主线程。

4.6 子线程中不能操作UI控件

Qt创建的子线程中是不能对UI对象进行任何操作的,即QWidget及其派生类对象。

每个程序在启动的时候都有一个线程,这个线程被称为“主线程”(在Qt应用里就是GUI线程)。

Qt的GUI必须运行在这个线程上。所有的窗口和几个相关的类,

例如QPixmap,不可以在另一个线程上运行(主要是事件循环在主线程里,窗口的绘制事件,用户的输入事件等等只会在事件事件循环中处理)。

其他线程一般当作工作线程来使用,可以用来减少主线程的负担,处理一些其他的工作。

4.7 安全的结束子线程

从Qt 4.8开始,可以通过将finish()信号连接到QObject::deleteLater()来释放存在于刚刚结束的线程中的对象。

可以通过调用 exit()或 quit()来停止线程。

可以使用 isDone()和 isRunning()来查询线程的状态。

建议使用的方法是在每次循环之前进行一个BOOL值的判断,
当BOOL值为假时,退出循环(当然,也可以使用terminate()粗暴的结束线程)。
当run函数里面没有循环时,函数像普通函数一样,运行完一次即退出函数。
线程结束时会发出信号,此时我们可以通过信号槽来销毁线程:
connect(thread,&QThread::finished
            ,thread,&QObject::deleteLater);//线程结束后调用deleteLater来销毁分配的内存

善用QObject::deleteLater 和 QObject::destroyed来进行内存管理

在new对象时候,直接用this指定其父类(即放入对象数中)
在程序最后自行释放资源   
   connect(this, &list::destroyed, this, [=]() {
        thread->quit();
        thread->wait();
        thread->deleteLater();
        myth->deleteLater();
        bub->deleteLater();
        });

4.8 QThread

QThread提供currentThreadId()方法返回当前的线程ID

qDebug()<<objectName()<<":"<<"getstarted() , tid :"<<QThread::currentThreadId();

QThread 还提供静态的、独立于平台的睡眠函数:sleep()、msleep() 和 usleep()分别允许全秒、毫秒和微秒分辨率。

静态函数 currentThreadId() 和 currentThread() 返回当前正在执行的线程的标识符。前者返回线程的平台特定 ID;后者返回一个 QThread 指针。

QThread是用来管理线程的,它所依附的线程和它管理的线程并不是同一个东西

QThread 所依附的线程,就是执行 QThread t(0) 或QThread * t=new QThread(0) 的线程。

QThread 管理的线程,就是 run启动的线程,也就是子线程。

如果QThread的对象依附在主线程中,其slot函数会在主线程中执行,而不是子线程。

跨不同线程与对象交互时必须小心。作为一般规则,函数只能从创建 QThread 对象本身的线程调用。

4.9 线程安全的事件传递

可重入和线程安全

静态方法QThread::postEvent从线程中传递事件,而不同于事件线程。

当使用Qt库互斥量的时候不要做任何阻塞操作。

确认你锁定一个递归QMutex的次数和解锁的次数一样,不能多也不能少。

在调用除了Qt容器和工具类的任何东西之前锁定Qt应用程序互斥量。

确认只在GUI线程中创建的继承和使用了QWidget、QTimer和QSocketNotifier的对象。

不要在不是GUI线程的线程中试图调用processEvents()函数。

不要把普通的Qt库和支持线程的Qt库混合使用。

所有的GUI类(比如,QWidget和它的子类),操作系统核心类(比如,QProcess)和网络类都不是线程安全的。

QRegExp使用一个静态缓存并且也不是线程安全的,即使通过使用QMutex来保护的QRegExp对象。

QT通过三种形式提供了对线程的支持。

一、平台无关的线程类,

二、线程安全的事件投递,

三、跨线程的信号-槽连接

Qt 线程类

Qt 包含下面一些线程相关的类:

QThread 提供了开始一个新线程的方法

QThreadStorage 提供逐线程数据存储

QMutex 提供相互排斥的锁,或互斥量

QMutexLocker 是一个便利类,它可以自动对QMutex加锁与解锁

QReadWriteLock 提供了一个可以同时读写操作的锁

QReadLocker与QWriteLocker 是便利类,它自动对QReadWriteLock加锁与解锁

QSemaphore 提供了一个整型信号量,是互斥量的泛化

QWaitCondition 提供了一种方法,使得线程可以在被另外线程唤醒之前一直休眠。

Qt 高级线程类

QtConcurrent 开启线程事务

QFutureWatcher 观测线程状态

QFuture 线程启动类

4.10 Qt 线程同步

QMutex, QReadWriteLock, QSemaphore, QWaitCondition 提供了线程同步的手段。

使用线程的主要想法是希望它们可以尽可能并发执行,

而一些关键点上线程之间需要停止或等待。

QMutex

QMutex 提供相互排斥的锁,或互斥量。在一个时刻至多一个线程拥有mutex,假如一个线程试图访问已经被锁定的mutex,那么它将休眠,直到拥有mutex的线程对此mutex解锁。Mutexes常用来保护共享数据访问。

QReadWriterLock

QReadWriterLock 与QMutex相似,除了它对 “read”,”write”访问进行区别对待。它使得多个读者可以共时访问数据。使用QReadWriteLock而不是QMutex,可以使得多线程程序更具有并发性。

QSemaphore

QSemaphore 是QMutex的一般化,它可以保护一定数量的相同资源,与此相对,一个mutex只保护一个资源。

QWaitCondition

QWaitCondition 允许线程在某些情况发生时唤醒另外的线程。一个或多个线程可以阻塞等待一QWaitCondition ,用wakeOne()或wakeAll()设置一个条件。wakeOne()随机唤醒一个,wakeAll()唤醒所有。


参考资料

Qt使用moveToThread( )正确的开启多线程、安全的退出线程_qt movetothread 退出线程-CSDN博客

[转] Qt多线程编程总结(一)-CSDN博客

https://www.cnblogs.com/xyf327/p/15032670.html

https://www.cnblogs.com/newstart/archive/2013/06/14/3136022.html

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【八股】AQS,ReentrantLock实现原理

AQS 概念 AQS 的全称是 AbstractQueuedSynchronized &#xff08;抽象队列同步器&#xff09;&#xff0c;在java.util.concurrent.locks包下面。 AQS是一个抽象类&#xff0c;主要用来构建锁和同步器&#xff0c;比如ReentrantLock, Semaphore, CountDownLatch&#xff0c;里…

Leetcode—163. 缺失的区间【简单】Plus

2024每日刷题&#xff08;126&#xff09; Leetcode—163. 缺失的区间 实现代码 class Solution { public:vector<vector<int>> findMissingRanges(vector<int>& nums, int lower, int upper) {int n nums.size();vector<vector<int>> an…

基于遗传优化模糊控制器的水箱水位控制系统simulink建模与仿真

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 4.1 模糊控制器原理 4.2 遗传算法原理 4.3 遗传优化模糊控制器的工作流程 5.完整工程文件 1.课题概述 基于遗传优化模糊控制器的水箱水位控制系统simulink建模与仿真。对比模糊控制器和基于遗传优化的…

Python基础详解一

一&#xff0c;print打印 print("hello word") print(hello word) 双引号和单引号都可以 二&#xff0c;数据类型 Python中常用的有6种值的类型 输出类型信息 print(type(11)) print(type("22")) print(type(22.2)) <class int> <class str&…

飞书API(7):MySQL 入库通用版本

一、引入 在上一篇介绍了如何使用 pandas 处理飞书接口返回的数据&#xff0c;并将处理好的数据入库。最终的代码拓展性太差&#xff0c;本篇来探讨下如何使得上一篇的最终代码拓展性更好&#xff01;为什么上一篇的代码拓展性太差呢&#xff1f;我总结了几点&#xff1a; 列…

深入理解 Java 并发:AbstractQueuedSynchronizer 源码分析

序言 在多线程编程中&#xff0c;同步机制是保障线程安全和协调线程之间操作顺序的重要手段。AQS 作为 Java 中同步机制的基础框架&#xff0c;为开发者提供了一个灵活且高效的同步工具。本文将通过对 AQS 源码的分析&#xff0c;解读 AQS 的核心实现原理&#xff0c;并深入探…

wireshark的安装使用及相关UDP、TCP、 ARP

初步了解&#xff1a; 进入wireshark后如图&#xff1a; 从图中可以看到很多网络连接在操作的时候我们需要监测哪些 我们可以直接在本地的运行框中输入ipconfig来查看 如图&#xff1a; 从以上图片中我们可以清楚地看到哪些网络连接已经连接的我们只需要按需监测他们即可 但…

【LinuxC语言】信号集与sigprocmask

文章目录 前言一、信号集1.1 操作信号集相关的函数1.2 信号屏蔽字1.3 sigprocmask1.4 示例代码 总结 前言 在Linux C编程中&#xff0c;信号是一种重要的进程间通信机制&#xff0c;用于通知进程发生了特定的事件。然而&#xff0c;程序在执行过程中可能会收到各种各样的信号&…

银河麒麟桌面版开机后网络无法自动链接 麒麟系统开机没有连接ens33

1.每次虚拟机开机启动麒麟操作系统&#xff0c;都要输入账号&#xff0c;密码。 进入点击这个ens33 内网才连接 2. 如何开机就脸上呢&#xff1f; 2.1. 进入 cd /etc/sysconfig/network-scripts 2.2 修改参数 onbootyes 改为yes 2.3 重启即可 a. 直接重启机器查看是否正常&…

软件工程习题答案2024最新版

习题一答案 一、选择题 软件的主要特性是(A B C)。 A) **无形 **B) 高成本 C) **包括程序和文档 ** D) 可独立构成计算机系统 软件工程三要素是(B)。 A) 技术、方法和工具 B) 方法、工具和过程 C) 方法、对象和类 D) 过程、模型、方法 包含风险分析的软件工程模型是(A)…

Reactor模型详解

目录 1.概述 2.Single Reactor 3.muduo库的Multiple Reactors模型如下 1.概述 维基百科对Reactor模型的解释 The reactor design pattern is an event handling pattern for handling service requests delivered concurrently to a service handler by one or more inputs.…

【Java基础】三大特性——封装

封装 只对外提供有用的属性和行为 方法的封装 外界不会用到的方法 class MyMath {//private私有 封装函数&#xff1a;只对外提供有用的属性和行为private void toAny(int num,int base,int offSet){……}public void toHex( int num){toAny( num,15,4);}…… } class Demo…

GNU Radio创建FFT、IFFT C++ OOT块

文章目录 前言一、GNU Radio官方FFT弊端二、创建自定义的 C OOT 块1、创建 OOT 模块2、创建 OOT 块3、修改 C 和 CMAKE 文件4、编译及安装 OOT 块 三、测试1、grc 图2、运行结果①、时域波形对比②、频谱图对比 四、资源自取 前言 GNU Radio 自带的 FFT 模块使用起来不是很方便…

新型直膨式光伏光热热泵/动力热管复合循环系统

太阳能光伏光热热泵&#xff08;即PVT热泵&#xff09;技术是建筑领域内实现碳中和的有效技术手段&#xff0c;该技术具有优越的热电冷联产能力。然而&#xff0c;现有的PVT热泵在良好的室外工况下能耗较高。为了解决这一问题&#xff0c;本文提出了一种新型的DX-PVT热泵/动力热…

书接上文,助力智能化诊断高质提效,基于轻量级CNN模型MobileNet开发构建人体手骨X光骨骼骨龄分析识别系统

骨龄是骨骼年龄的简称&#xff0c;需要借助于骨骼在X光摄像中的特定图像来确定。通常要拍摄左手手腕部位的X光片&#xff0c;医生通过X光片观察来确定骨龄。这在临床上是一件非常消耗精力和时间的一项放射临床工作。写一个骨龄可能要10多分钟去完成。如果一天要写几十个骨龄&am…

10G MAC层设计系列-(4)MAC TX模块

一、前言 MAC TX模块就是要将IP层传输过来的数据封装前导码、MAC地址、帧类型以及进行CRC校验&#xff0c;并与CRC值一块组成以太网帧。 二、模块设计 首先对输入的数据进行缓存&#xff0c;原因是在之后要进行封装MAC帧头&#xff0c;所以需要控制数据流的流动 FIFO_DATA_6…

基于K8S构建Jenkins持续集成平台

文章目录 安装和配置NFSNFS简介NFS安装 在Kubernetes安装Jenkins-Master创建NFS client provisioner安装Jenkins-Master Jenkins与Kubernetes整合实现Jenkins与Kubernetes整合构建Jenkins-Slave自定义镜像 JenkinsKubernetesDocker完成微服务持续集成拉取代码&#xff0c;构建镜…

茶树(山茶属)CCoAOMT基因家族的全基因组鉴定、表达分析和蛋白质相互作用分析-全基因组家族分析-文献精读13

Genome-wide identification, expression profiling, and protein interaction analysis of the CCoAOMT gene family in the tea plant (Camellia sinensis) 茶树&#xff08;山茶属&#xff09;CCoAOMT基因家族的全基因组鉴定、表达分析和蛋白质相互作用分析&#xff0c;一篇…

详解SDRAM基本原理以及FPGA实现读写控制(一)

文章目录 一、SDRAM简介二、SDRAM存取结构以及原理2.1 BANK以及存储单元结构2.2 功能框图2.3 SDRAM速度等级以及容量计算 三、SDRAM操作命令3.1 禁止命令&#xff1a; 4b1xxx3.2 空操作命令&#xff1a;4b01113.3 激活命令&#xff1a;4b00113.4 读命令&#xff1a;4b01013.5 写…

5分钟速通大语言模型(LLM)的发展与基础知识

✍️ 作者&#xff1a;哈哥撩编程&#xff08;视频号同名&#xff09; 博客专家全国博客之星第四名超级个体COC上海社区主理人特约讲师谷歌亚马逊演讲嘉宾科技博主极星会首批签约作者 &#x1f3c6; 推荐专栏&#xff1a; &#x1f3c5; 程序员&#xff1a;职场关键角色通识宝…
最新文章