当前位置:   article > 正文

Qt多线程详解及与事件循环、信号槽、线程设计、GUI组件通信之间的关系_gui线程是什么

gui线程是什么

一、线程概念

GUI线程与工作线程:每个程序启动后拥有的第一个线程称为主线程,即GUI线程。QT中所有的组件类和几个相关的类只能工作在GUI线程,不能工作在次线程,次线程即工作线程,主要负责处理GUI线程卸下的工作。

数据的同步访问:每个线程都有自己的栈,因此每个线程都要自己的调用历史和本地变量。线程共享相同的地址空间

1.Qt主线程

Qt的主线程是唯一运行创建QApplication对象并调用exec()的线程,主要用于界面显示,因此又被称为GUI线程。

2.Qt子线程

Qt的子线程用于一些耗时操作,因此又被称为工作线程。

子线程不能用于直接刷新界面(QWidget不可重入,QObject可重入)。

若子线程企图修改界面控件,可通过线程间通信的方式:Qt的信号槽机制是跨线程的,因此可以用作线程间通信。

主线程是唯一允许创建QApplication或者QCoreApplication对象的,并且调用exec()。exec()启动了事件循环,一直在等待接收并且处理一个个Qt封装好的事件,比如鼠标移动事件,键盘按下事件等等。所以只有在主线程里你才可以方便利用各种Event去完成自己想要实现的需求。所以就限制你必须在主线程作UI相关操作。

除了规定,往根源说,再多的线程,实质上对于CPU来说,也是一件一件的处理,并不是我们凭空现象的同时处理。只是可以“智能”的处理一下当前迫切需要的数据,然后可以随时暂停,再去处理更加迫切的。如果2个线程同时处理UI显示,一个线程正在用于和用户交互更新显示,另外一个线程就只能是等待状态,并不能完成我们所期望的同时刷新UI的期望。

所以,基本上大部分应用开发框架都限制更新、创建UI必须在主线程里完成,而逻辑运算可以匹出新线程去完成。

二、简介QThread类

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

QThread 所依附的线程,就是执行 QThread t(0) 或 QThread * t=new QThread(0) 的线程。也就是咱们这儿的主线程.

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

因为QThread的对象依附在主线程中,所以他的slot函数会在主线程中执行,而不是次线程。除非:
(1) QThread 对象依附到次线程中(通过movetoThread)
(2) slot 和信号是直接连接,且信号在次线程中发射
但上两种解决方法都不好,因为QThread不是这么用的.

一、公共槽函数:

1)start():开始线程的执行,内部调用run()函数,run()又调用exec()。

2)quit():告诉线程的事件循环停止运行,并返回0(成功),等价于调用exit(0)。调用exit后,thread将退出EventLoop,并从exec返回;

3)terminiate():「不推荐使用该函数」终止线程的执行。线程可能不会立即终止,取决于操作系统的调度。

二、信号:

1)started():在start()之后,在run()之前发射。

2)funished():在线程将要停止执行时发射。发射时,线程已经停止事件循环。不再执行新的事件,但是deferred deletion事件除外。可以把该信号连接到本线程中的对象的deleteLater()槽中。

三、公共函数:

wait():等待线程停止执行,通常和quit()配合使用

void msleep(unsigned long msecs) [static]
 
强制当前线程睡眠msecs毫秒。
 
void sleep(unsigned long secs) [static]
 
强制当前线程睡眠secs秒。
 
void usleep(unsigned long usecs) [static]
 
强制当前线程睡眠usecs微秒。
 
bool wait(unsigned long time = ULONG_MAX)
 
线程将会被阻塞,等待time毫秒。和sleep不同的是,如果线程退出,wait会返回
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

三、线程与事件循环

QThread中run()的默认实现调用了exec(),从而创建一个QEventLoop对象,由QEventLoop对象处理线程中事件队列(每一个线程都有一个属于自己的事件队列)中的事件。exec()在其内部不断做着循环遍历事件队列的工作,调用QThread的quit()或exit()方法使退出线程,尽量不要使用terminate()退出线程,terminate()退出线程过于粗暴,造成资源不能释放,甚至互斥锁还处于加锁状态。

线程中的事件循环,使得线程可以使用那些需要事件循环的非GUI 类(如:QTimer,QTcpSocket,QProcess)。

在QApplication前创建的对象,QObject::thread()返回NULL,意味着主线程仅为这些对象处理投递事件,不会为没有所属线程的对象处理另外的事件。

可以用QObject::moveToThread()来改变对象及其子对象的线程亲缘关系,假如对象有父亲,不能移动这种关系。

在另一个线程(而不是创建它的线程)中delete QObject对象是不安全的。除非可以保证在同一时刻对象不在处理事件。可以用QObject::deleteLater(),它会投递一个DeferredDelete事件,这会被对象线程的事件循环最终选取到。

假如没有事件循环运行,事件不会分发给对象。假如在一个线程中创建了一个QTimer对象,但从没有调用过exec(),那么QTimer就不会发射它的timeout()信号,deleteLater()也不会工作。可以手工使用线程安全的函数QCoreApplication::postEvent(),在任何时候,给任何线程中的任何对象投递一个事件,事件会在那个创建了对象的线程中通过事件循环派发。

事件过滤器在所有线程中也被支持,不过它限定被监视对象与监视对象生存在同一线程中。QCoreApplication::sendEvent(不是postEvent()),仅用于在调用此函数的线程中向目标对象投递事件。

四、线程与信号槽

1、线程的依附性
线程的依附性是对象与线程的关系。默认情况下,对象依附于自身被创建的线程。

对象的依附性与槽函数执行的关系,默认情况下,槽函数在其所依附的线程中被调用执行。

修改对象的依附性的方法:QObject::moveToThread函数用于改变对象的线程依附性,使得对象的槽函数在依附的线程中被调用执行。

2、QObject与线程
QThread类具有发送信号和定义槽函数的能力。QThread主要信号如下:

void started(); 线程开始运行时发送信号

void finished(); 线程完成运行时发送信号

void terminated(); 线程被异常终止时发送信号

QThread继承自QObject,发射信号以指示线程执行开始与结束,并提供了许多槽函数。QObject可以用于多线程,发射信号以在其它线程中调用槽函数,并且向“存活”于其它线程中的对象发送事件。

3.QObject的可重入性
QObject是可重入的,QObject的大多数非GUI子类如 QTimer、QTcpSocket、QUdpSocket、QHttp、QFtp、QProcess也是可重入的,在多个线程中同时使用这些类是可能的。可重入的类被设计成在一个单线程中创建与使用,在一个线程中创建一个对象而在另一个线程中调用该对象的函数,不保证能行得通。
有三种约束需要注意:

A、一个QObject类型的孩子必须总是被创建在它的父亲所被创建的线程中。这意味着,除了别的以外,永远不要把QThread对象(this)作为该线程中创建的一个对象的父亲(因为QThread对象自身被创建在另外一个线程中)。

B、事件驱动的对象可能只能被用在一个单线程中。特别适用于计时器机制(timer mechanism)和网络模块。例如:不能在不属于这个对象的线程中启动一个定时器或连接一个socket,必须保证在删除QThread之前删除所有创建在这个线程中的对象。在run()函数的实现中,通过在栈中创建这些对象,可以轻松地做到这一点。

C、虽然QObject是可重入的,但GUI类,尤其是QWidget及其所有子类都不是可重入的,只能被用在GUI线程中。QCoreApplication::exec()必须也从GUI线程被调用。

在实践中,只能在主线程而非其它线程中使用GUI的类,可以很轻易地被解决:将耗时操作放在一个单独的工作线程中,当工作线程结束后在GUI线程中由屏幕显示结果。

一般来说,在QApplication前创建QObject是不行的,会导致奇怪的崩溃或退出,取决于平台。因此,不支持QObject的静态实例。一个单线程或多线程的应用程序应该先创建QApplication,并最后销毁QObject。

4.线程的事件循环
每个线程都有自己的事件循环。主线程通过QCoreApplication::exec()来启动自己的事件循环,但对话框的GUI应用程序,有些时候用QDialog::exec(),其它线程可以用QThread::exec()来启动事件循环。就像 QCoreApplication,QThread提供一个exit(int)函数和quit()槽函数。

线程中的事件循环使得线程可以利用一些非GUI的、要求有事件循环存在的Qt类(例如:QTimer、QTcpSocket、和QProcess),使得连接一些线程的信号到一个特定线程的槽函数成为可能。

**一个QObject实例被称为存活于它所被创建的线程中。关于这个对象的事件被分发到该线程的事件循环中。**可以用QObject::thread()方法获取一个QObject所处的线程。

QObject::moveToThread()函数改变一个对象和及其子对象的线程所属性。(如果对象有父对象的话,对象不能被移动到其它线程中)。

从另一个线程(不是QObject对象所属的线程)对该QObject对象调用delete方法是不安全的,除非能保证该对象在那个时刻不处理事件,使用QObejct::deleteLater()更好。一个DeferredDelete类型的事件将被提交(posted),而该对象的线程的 件循环最终会处理这个事件。默认情况下,拥有一个QObject的线程就是创建QObject的线程,而不是 QObject::moveToThread()被调用后的。

如果没有事件循环运行,事件将不会传递给对象。例如:在一个线程中创建了一个QTimer对象,但从没有调用exec(),那么,QTimer就永远不会发射timeout()信号,即使调用deleteLater()也不行。(这些限制也同样适用于主线程)。

利用线程安全的方法QCoreApplication::postEvent(),可以在任何时刻给任何线程中的任何对象发送事件,事件将自动被分发到该对象所被创建的线程事件循环中。

所有的线程都支持事件过滤器,而限制是监控对象必须和被监控对象存在于相同的线程中。QCo

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/一键难忘520/article/detail/854112
推荐阅读
相关标签
  

闽ICP备14008679号