总结:QT 多线程(处理密集时的界面响应保持)

您所在的位置:网站首页 多线程处理同一批数据的函数 总结:QT 多线程(处理密集时的界面响应保持)

总结:QT 多线程(处理密集时的界面响应保持)

2024-07-10 09:07| 来源: 网络整理| 查看: 265

目录

前因:

起因:如果处理一个特定任务上耗费的时间过多时,那么用户界面就会变得无法响应。

问题:怎么保持在程序密集响应时,界面不会卡住?

在此种情况下的解决方案:

一、利用processEvents()函数

二、使用多线程(QT开启多线程的三种方式)

1、继承QThread重写虚函数run()

2、moveToThread

3、QtConCurrent::run()并发

 三、QThread线程安全释放顺序

四、同步线程

☋·QMutex

☋·QReadWriteLock

五、在次线程中使用qt类(QObject是可重入的,但必须记住他有三个约束条件)

☋·QObject的子对象必须在它的父对象线程中创建

☋·在删除对应的QThread对象之前,必须删除所有在次线程中创建的QObject对象

☋·必须在创建QObject对象的线程中删除它们

前因:

当调用QApplication::exec()时,就启动了QT的事件循环。在开始的时候QT会发出一些事件命令来显示和绘制窗口部件。

在这之后,事件循环就开始运行,它不断检查是否有事件发生并且把这些事件发生给应用程序的QObject。

当处理一个事件时,也可能同时产生一些其他的事件并且将其追加到QT的事件队列中。如果在处理一个特定事件上耗费的事件过多,那么用户界面将变得无法响应。例如,在应用程序把一个文件保存到磁盘的过程中,直到文件保存完毕,才会处理那些由窗口系统产生的事件;在文件保存的过程中,应用程序就不能响应来自窗口系统重新绘制的请求。

起因:如果处理一个特定任务上耗费的时间过多时,那么用户界面就会变得无法响应。 问题:怎么保持在程序密集响应时,界面不会卡住? 在此种情况下的解决方案: 一、利用processEvents()函数

在代码中频繁调用该函数即可QApplication::processEvents()。这个函数告诉QT处理所有那些还没有被处理的各类事件,然后将控制权返回给调用者。 实际上,QApplication::processEvents()就是一个不停调用processEvent()函数的while循环。

示例:

void test::writeFile(const QString &sFileName) { QFile f(sFileName); …… for(int i=0;iprocessEvent(); } }

使用这个方法的时候存在一个潜在的问题:应用程序还在执行的时候,就关闭了主窗口或者点击了其他响应,会产生预料不到的后果。解决方案:替换成QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); 以告诉QT忽略鼠标事件和键盘事件。

 补充:QMetaObject::invokeMethod()使用解决界面卡住问题

二、使用多线程(QT开启多线程的三种方式)

一个线程用于处理应用程序中的用户界面,另一个线程则执行文件保存操作(或任意其他耗时的操作),这样的话,在保存文件的时候,应用程序的用户界面仍可以保持响应。

1、继承QThread重写虚函数run()

示例: 

☋.h

☋ .cpp里放耗时的操作

调用:开启线程,调用start之后就会执行run函数了。

SendMsgThread *smt = new SendMsgThread(); smt->start(); 2、moveToThread

原型:void QObject::moveToThread ( QThread * targetThread )子类化QObject

如何利用moveToThread开启多线程: 第一步:创建一个继承QObject的子类,在这里起的类名假定为MyObject,在类里定义一个槽函数doWorker()

第二步:使用moveToThread的方法创建线程:

·☋ 实例化MyObject

MyObject *myobject1 = new MyObject();

·☋ 实例化QThread

QThread *thread1 = new QThread();

·☋ 将创建出来的MyObject加入到QThread中

myobject1->moveToThread(thread1);

·☋ 将信号与槽关联起来,自带的线程信号有两个,一个是started,一个是finished,顾名思义,分别是当线程启动和结束的时候.这里只展示started的,可以使用自己自定义的信号

QObject::connect(thread1,SIGNAL(started()), myobject1,SLOT(doWorker()),Qt::QueuedConnection);

·☋ 最后一步线程开启后,就会在线程里面执行函数里面的内容

thread1->start();

注:同一个QObject的对象只能对应一个线程

 

3、QtConCurrent::run()并发

QtConcurrent这是一个高级 API,构建于QThreadPool之上,它提供更高层次的函数接口(APIs),使所写的程序,可根据计算机的CPU核数,自动调整运行的线程数量.主要功能是令启动一个线程来执行一个函数. 注意,QtConcurrent是一个命名空间而不是一个类,因此其中的所有函数都是命名空间内的全局函数。使用时pro文件要添加:QT += concurrent 头文件:

使用方法:QtConcurrent::run()可以开辟一个单独的线程来运行我们定义的函数(可以在里面执行一些耗时或者需要并行的计算),QtConcurrent::run()返回一个QFuture模板类,用来处理函数的返回值。

相关函数说明:QFuture future;//可以获得计算的结果值

future.waitForFinished();等待线程结束,实现阻塞future. isFinished() 判断线程是否结束future.isRunning() 判断线程是否在运行future.result()取出线程函数的返回值

示例:根据线程执行的函数可分为以下三种 1 全局函数或静态函作为线程函数并且线程执行不带参数 QFuture QtConcurrent::run(Function function, …),例:

2 线程执行类成员函数(带参数):run的第一个参数必须是const引用或者对象指针

 

3 结构体函数作为线程函数

sturct worker { int ID; void worker::threadFunc() { } }; int main() { worker work; QtConcurrent::run(&work,&worker::threadFunc);  }  三、QThread线程安全释放顺序 QThread *thread = new QThread(); thread->quit(); thread->deleteLater(); thread = NULL;//避免成为野指针 thread->wait();

quit()——停止线程的循环事件,如果线程没有事件循环则什么都不做;

wait()——会阻塞到线程执行完,才执行wait后面的代码,线程退出,wait会返回;

deleteLater()——删除某个对象,防止内存泄露;同一个对象调用多次deleteLater()不会照成多重删除;但并不是立即执行deleteLater(),而是

>当Object回到事件循环中,对象将会被删除;

>线程中如果没有事件循环,那么当线程完成后就会被删除  

还有terminate()函数也可以终止线程,但是这种方法并不安全,因为它可以随时停止线程而不给这个线程自我情况的机会。

四、同步线程

对于多线程应用程序,一个最基本要求就是能实现几个线程的同步执行,QT提供了一下几个用于同步的类:QMutex、QReadWriteLock、QSemaphore和QWaitCondition

☋·QMutex

提供了一种保护一个变量或一段代码的方法,这样就可以每次只让一个线程读取它。

原理:这个类提供了一个lock()函数来锁住互斥量mutex,如果互斥量是解锁的unlock,那么当前线程就立即占用并锁定lock它;否则,当前线程就会被阻塞,直到掌握这个互斥量的线程对它解锁为止。QMutex类还提供一个tryLock()函数,如果互斥量已经锁住,它就会立即返回。

示例:

QMutex mutex; void test() { mutex.lock(); 代码…… mutex.unlock(); }

使用互斥量存在一个问题:每次只能有一个线程可以访问同一变量。在程序中可能会有多线程同时尝试访问读取同一变量(不修改),此时互斥量可能就会成为一个严重的性能瓶颈。在这种情况下,可以使用QReadWriteLock,他是一个同步类,允许同时执行多个读取访问而不会影响性能。

☋·QReadWriteLock

a.读写锁的特性:读共享,写独占。读共享 : 当其他线程占用读锁的时候,如果其他线程请求读锁,会立即获得。 当其他线程占用读锁的时候,如果其他线程请求写锁,会阻塞等待读锁的释放。写独占 : 当其他线程占用写锁的时候,如果其他线程请求读锁,会阻塞等待写锁的释放。 当其他线程占用写锁的时候,如果其他线程请求写锁,会阻塞等待写锁的释放。

b.读写优先级 默认优先级是写优先,即写锁的优先级>读锁,哪怕是读先排队的也没用。

3、常用函数包含:

lockForRead() ;请求读锁lockForWrite() ;请求写锁tryLockForRead() ;尝试请求读锁,非阻塞函数,可以设置超时时间tryLockForWrite() ;尝试请求写锁,非阻塞函数,可以设置超时时间unlock() ;解锁(解读锁和解写锁,均使用该函数)

示例:

Mydata data; QReadWriteLock lock; void ReadThread::run() { …… lock.lockForRead(); 读data …… lock.unlock(); } void WriteThread::run() { …… lock.lockForWrite(); …… 写data lock.unlock(); }

为简便起见,我们可以使用QreadLocker类和QWriteLocker类对QReadWriteLock进行锁定和解锁。

五、在次线程中使用qt类(QObject是可重入的,但必须记住他有三个约束条件)

当函数可以同时被不同的线程安全地调用时,就称其为线程安全的。

可重入:如果类的不同实例可同时用于不同的线程,那么这个类就是重入的。

然而在多个线程中同时访问同一个可重入对象是不安全的,而是应该用一个互斥量来保护这个类的访问。一个类是否可重入,在qt参考文档有标记。通常情况下,任何没有被全局引用或者被其他数据引用的c++类都认为是可重入的。

QObject是可重入的,但是必须记住他有三个约束条件:

☋·QObject的子对象必须在它的父对象线程中创建

特别需要说明的是,这一约束条件意味着,在次线程中创建的对象永远不能将QThread对象作为创建他们的父对象,因为QThread对象是在另一个线程(主线程或另外一个不同的次线程中创建的)。非常重要,不然会报错,你的父对象是另一个的子对象,大体就是这个意思

☋·在删除对应的QThread对象之前,必须删除所有在次线程中创建的QObject对象

通过在QThread::run()中的堆栈上创建这些对象,就可以完成这一点。

☋·必须在创建QObject对象的线程中删除它们

如果需要删除一个存在于不同线程中的QObject对象,必须调用线程安全的QObject::deleteLater函数,它可以置入一个延期删除的事件。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3