前因:
当调用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;i<size;i++)
{
代码
qApp->processEvent();
}
}
使用这个方法的时候存在一个潜在的问题:应用程序还在执行的时候,就关闭了主窗口或者点击了其他响应,会产生预料不到的后果。解决方案:替换成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函数,它可以置入一个延期删除的事件。