首页 > 其他分享 >笔记:Qt开发之多线程的实现(QThread、moveToThread)

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

时间:2023-12-12 18:44:57浏览次数:36  
标签:moveToThread Qt QObject 线程 多线程 QThread 函数

目标:了解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 parent2,把子线程的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 QThreadQThread提供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 提供了线程同步的手段。使用线程的主要想法是希望它们可以尽可能并发执行,而一些关键点上线程之间需要停止或等待。
QMutexQMutex 提供相互排斥的锁,或互斥量。在一个时刻至多一个线程拥有mutex,假如一个线程试图访问已经被锁定的mutex,那么它将休眠,直到拥有mutex的线程对此mutex解锁。Mutexes常用来保护共享数据访问。
QReadWriterLockQReadWriterLock 与QMutex相似,除了它对 “read”,”write”访问进行区别对待。它使得多个读者可以共时访问数据。使用QReadWriteLock而不是QMutex,可以使得多线程程序更具有并发性。
QSemaphoreQSemaphore 是QMutex的一般化,它可以保护一定数量的相同资源,与此相对,一个mutex只保护一个资源。
QWaitConditionQWaitCondition 允许线程在某些情况发生时唤醒另外的线程。一个或多个线程可以阻塞等待一QWaitCondition ,用wakeOne()或wakeAll()设置一个条件。wakeOne()随机唤醒一个,wakeAll()唤醒所有。

参考资料https://blog.csdn.net/weixin_48424192/article/details/110367584 http://blog.csdn.net/emdfans/article/details/41745007 https://www.cnblogs.com/xyf327/p/15032670.html https://www.cnblogs.com/newstart/archive/2013/06/14/3136022.html






标签:moveToThread,Qt,QObject,线程,多线程,QThread,函数
From: https://www.cnblogs.com/bog-box/p/17897582.html

相关文章

  • 使用C++和QT实现Log自定义日志系统
    MyLog说明使用QT的qInstallMessageHandler函数结合qDebug,qInfo实现自定义的日志系统输出日志到文件和控制台自动检测日志文件大小自动更新日志文件修改日期自动备份自动删除一个月前的日志文件支持多线程程序支持扩展,可输出日志到数据库,网络,或服务器支持扩展,可使用co......
  • 笔记:Qt开发之定制化qDebug()函数
    目标:实现qDebug()函数的定制输出,包含文件名、函数名、行数等信息1,通过qSetMessagePattern函数,实现定制化输出intmain(intargc,char**argv){QApplicationapp(argc,argv);//改变缺省消息处理程序的输出,信息:线程:功能函数(行数):时间qSetMessagePattern("%{mes......
  • 【转载】QT学习之路(一)ubuntu 18.04的Qt Creator在线安装
    https://blog.csdn.net/qq_26849933/article/details/127115102前言Qt是嵌入式开发的必备工具之一,在Linux下安装尤其重要。Qt是C++的一个库,或者说是开发框架,里面集成了一些库函数,提高开发效率。QtCreator是一个IDE,就是一个平台,一个开发环境,类似的比如说VS,也可以进行Qt开发,当......
  • C++ Qt开发:SpinBox数值微调框组件
    Qt是一个跨平台C++图形界面开发库,利用Qt可以快速开发跨平台窗体应用程序,在Qt中我们可以通过拖拽的方式将不同组件放到指定的位置,实现图形化开发极大的方便了开发效率,本章将重点介绍QSpinBox精度数值组件的常用方法及灵活运用。QSpinBox是Qt框架中的一个部件(Widget),用于提供一个方......
  • Qt - 操作注册表
    一、写入注册表#include<QSettings>//实例QSettings//参数1:注册表位置//参数2:操作windows注册表QSettings::NativeFormat//说明:QSettings::IniFormat读写ini格式的配置文件,前面用过。QSettings*reg=newQSettings("HKEY_CURRENT_USER\\Software\\Qt01",QSett......
  • 多线程爬虫抓取京东运行流程-大公司抢着要代码
    之前有个大公司找我,需要爬取京东有关行业商家的价格信息做对比,方便后期自己的产品定位以及舆情监控,让我写一个通用的爬虫模版,方便他们那边技术调整修改,于是带着这样的问题,我给了他们一些几点建议。首先,你需要安装必要的库,包括HTTP库、JSON库、爬虫库、代理库和可视化库。可以使......
  • Qt中QWidgetAction使用例子
    在Qt中可以用QWidgetAction来模仿现代应用程序中的自定义控件的菜单项。比如下方Edge浏览器的菜单,在“缩放”一项中有用来调整网页缩放的几个按钮和显示网页缩放比例的控件。这样的菜单项Qt也可以做。下面将给出一个简单的例子供参考,此例子在VS2017和Qt5.9下测试通过。其运行效......
  • Java多线程编程
    本文中简单介绍一些java多线程相关的内容1.多线程基础Java通过java.lang.Thread类和java.util.concurrent包提供了多线程支持。一个线程可以通过继承Thread类或实现Runnable接口来创建。classMyThreadextendsThread{publicvoidrun(){//线程执行的代码}......
  • C++ Qt开发:PushButton按钮组件
    Qt是一个跨平台C++图形界面开发库,利用Qt可以快速开发跨平台窗体应用程序,在Qt中我们可以通过拖拽的方式将不同组件放到指定的位置,实现图形化开发极大的方便了开发效率,本章将重点介绍QPushButton按钮组件的常用方法及灵活运用。QPushButton是Qt框架中用于创建按钮的组件类,是QW......
  • qt和cmake安装
    1/qtIndexof/archive/qt  2、cmakeDownloadCMake 安装比较简单 记得选择addpath,这样就不用手动添加环境变量了 3、vscode插件c/c++ cmake cmaketools qtconfigure qttools ......