Qt之Threads和QObjects

简述

QThread继承自QObject,它发射信号(signals)以表明线程执行开始或结束,并提供了一些槽函数(slots)。

更有趣的是,QObjects可以在多线程中使用,发射信号以在其它线程中调用槽函数,并且向“存活”于其它线程中的对象发送事件(post events)。这是可能的,因为每一个线程都拥有它自身的事件循环(event loop)。

  • 简述
  • QObject可重入性
  • 每个线程的事件循环
  • 从其它线程访问QObject子类
  • 跨线程的信号和槽

QObject可重入性

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

  • 一个QObject类型的孩子必须总是被创建在它的父亲所被创建的线程中。这意味着,除了别的以外,永远不要把QThread对象(this)作为该线程中创建的一个对象的父亲(因为QThread对象自身被创建在另外一个线程中)。
  • 事件驱动的对象可能只能被用在一个单线程中。特别是,这适用于计时器机制(timer mechanism)和网络模块。例如:你不能在不属于这个对象的线程中启动一个定时器或连接一个socket,必须保证在删除QThread之前删除所有创建在这个线程中的对象。在run()函数的实现中,通过在栈中创建这些对象,可以轻松地做到这一点。
  • 虽然QObject是可重入的,但GUI类,尤其是QWidget及其所有子类都不是可重入的,它们只能被用在主线程中。如前面所述,QCoreApplication::exec()必须也从那个线程被调用。

在实践中,只能在主线程而非其它线程中使用GUI的类这一点,可以很轻易地被解决:将耗时操作放在一个单独的worker线程中,当worker线程结束后在主线程中由屏幕显示结果。这个方法被用来实现Mandelbrot Example和the Blocking Fortune Client Example。

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

每个线程的事件循环

每个线程都有它自己的事件循环。初始线程通过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(),你可以在任何时刻给任何线程中的任何对象发送事件,这些事件将自动被分发到该对象所被创建的线程事件循环中。

所有的线程都支持事件过滤器,而限制是监控对象必须和被监控对象存在于相同的线程中。类似的,QCoreApplication::sendEvent()(不同于postEvent())只能将事件分发到和该函数调用者相同的线程中的对象。

从其它线程访问QObject子类

QObject及其所有子类都不是线程安全的。这包含了整个事件交付系统。重要的是,切记事件循环可能正在向你的QObject子类发送事件,当你从另一个线程访问该对象时。

如果你正在调用一个QObject子类的函数,而该子类对象并不存活于当前线程中,并且该对象是可以接收事件的,那么你必须用一个mutex保护对该QObject子类的内部数据的所有访问,否则,就有可能发生崩溃和非预期的行为。

同其它对象一样,QThread对象存活于该对象被创建的线程中 – 而并非是在QThread::run()被调用时所在的线程。一般来说,在QThread子类中提供槽函数是不安全的,除非用一个mutex保护成员变量。

另一方面,可以在QThread::run()的实现中安全地发射信号,因为信号发射是线程安全的。

跨线程的信号和槽

Qt支持如下的信号-槽连接类型:

  • Auto Connection(默认):如果信号在接收者所依附的线程内发射,则等同于Direct Connection。否则,等同于Queued Connection。
  • Direct Connection:当信号发射后,槽函数立即被调用。槽函数在信号发射者所在的线程中执行,而未必需要在接收者的线程中。
  • Queued Connection:当控制权回到接受者所在线程的事件循环时,槽函数被调用。槽函数在接收者的线程中执行。
  • Blocking Queued Connection:槽函数的调用情形和Queued Connection相同,不同的是当前的线程会阻塞住,直到槽函数返回。
    注意:在同一个线程中使用这种类型进行连接会导致死锁。
  • Unique Connection:行为与Auto Connection相同,但是连接只会在“不会与已存在的连接相同”时建立,也就是:如果相同的信号已经被连接到相同的槽函数,那么连接就不会被再次建立,并且connect()会返回false。

通过传递一个参数给connect()来指定连接类型。要知道,如果一个事件循环运行在接收者的线程中,而发送者和接收者位于不同的线程时,使用Direct Connection是不安全的。同样的原因,调用存活于另一个线程中的对象的任何函数都是不安全的。

QObject::connect()本身是线程安全的。

Mandelbrot Example使用了Queued Connection来连接一个worker线程和主线程。为了避免冻结主线程的事件循环(即避免因此而冻结了应用的UI),所有的曼德尔布罗特分形计算(Mandelbrot fractal computation)都是在一个单独的worker线程中完成的,线程结束一个计算时发射一个信号。

同样,Blocking Fortune Client Example使用了一个单独的线程来和TCP server进行异步通信。

时间: 2024-08-02 21:12:36

Qt之Threads和QObjects的相关文章

《Qt 实战一二三》

简介 "我们来自Qt分享&&交流,我们来自Qt Quick分享&&交流",不管你是笑了,还是笑了,反正我们是认真的.我们就是要找寻一种Hold不住的状态,来开始每一天的点滴分享,我们是一个有激情,有态度的部队. 但是我们还是我们,我们只是多了一份责任.古语有云:"不积跬步无以至千里,不积小流无以成江海",所以每一个伟大事务的产生都不是一蹴而就的.现在我们要立足眼下,把第一站放在地球,"<Qt 实战一二三>&quo

Qt之QThread(深入理解)

简述 为了让程序尽快响应用户操作,在开发应用程序时经常会使用到线程.对于耗时操作如果不使用线程,UI界面将会长时间处于停滞状态,这种情况是用户非常不愿意看到的,我们可以用线程来解决这个问题. 前面,已经介绍了QThread常用的两种方式: Worker-Object 子类化QThread 下面,我们来看看子类化QThread在日常中的应用. 简述 子类化QThread 线程休眠 在主线程中更新UI 避免多次connect 优雅地结束线程 更多参考 大多数情况下,多线程耗时操作会与UI进行交互,比

Qt之QThreadPool和QRunnable

简述 QRunnable 是所有 runnable 对象的基类,而 QThreadPool 类用于管理 QThreads 集合. QRunnable 类是一个接口,用于表示一个任务或要执行的代码,需要重新实现 run() 函数. QThreadPool 管理和循环使用单独的 QThread 对象,以帮助程序减少创建线程的成本.每个 Qt 应用程序都有一个全局 QThreadPool 对象,可以通过调用 globalInstance() 访问. 简述 详细描述 基本使用 自定义信号槽 更多参考 详

关于在windows下部署发布QT程序的总结

关于在windows下部署发布QT程序的总结 文章出处:http://www.diybl.com/course/3_program/c++/cppjs/200869/123842.html 以下包括了部分网上收集的,以及qt帮助里的内容(Deploying an Application on Qt/Windows) 首先,打开windows控制台,然后,找到vs安装目录下的bin里,执行 vcvars32.bat 这个脚本.执行完之后,vs需要的所有环境变量就已经设置好了. 然后,做下面的操作:

Qt之线程基础

何为线程 线程与并行处理任务息息相关,就像进程一样.那么,线程与进程有什么区别呢?当你在电子表格上进行数据计算的时候,在相同的桌面上可能有一个播放器正在播放你最喜欢的歌曲.这是一个两个进程并行工作的例子:一个进程运行电子表格程序:另一个进程运行一个媒体播放器.这种情况最适合用多任务这个词来描述.进一步观察媒体播放器,你会发现在这个进程内,又存在并行的工作.当媒体播放器向音频驱动发送音乐数据的时候,用户界面上与之相关的信息不断地进行更新.这就是单个进程内的并行线程. 那么,并发是如何实现的呢?在单

Qt之QThread

简述 QThread类提供了与系统无关的线程. QThread代表在程序中一个单独的线程控制.线程在run()中开始执行,默认情况下,run()通过调用exec()启动事件循环并在线程里运行一个Qt的事件循环. 简述 详细描述 线程管理 使用方式 worker-object 子类化QThread 耗时操作 详细描述 当线程started()和finished()时,QThread会通过一个信号通知你,可以使用isFinished()和isRunning()来查询线程的状态. 你可以通过调用exi

Qt之对象树与所有权

简述 QObjects在一个对象树中组织他们自己.当创建一个QObject时,如果使用了其他对象作为其父对象,那么,它就会被添加到父对象的children()列表中.这样一来,当父对象被销毁时,这个QObject也会被销毁.事实表明,这个机制非常适合于管理GUI对象.例如:一个QShortcut(键盘快捷键)对象是相关窗口的一个子对象,所以,当用户关闭了这个窗口时,快捷键也会被销毁. 简述 详细描述 QObjects的构造销毁顺序 详细描述 QQuickItem是Qt Quick模块的基本视觉元

Qt之QEvent

简述 QEvent 类是所有事件类的基类,事件对象包含事件参数. Qt 的主事件循环(QCoreApplication::exec())从事件队列中获取本地窗口系统事件,将它们转化为 QEvents,然后将转换后的事件发送给 QObjects. 一般来说,事件来自底层窗口系统(spontaneous() 返回 true),但也可以使用 QCoreApplication::sendEvent() 和 QCoreApplication::postEvent()(spontaneous() 返回 fa

【C/C++学院】0822-类型转换函数与构造转换函数/类的继承/类的继承以及区别/继承静态成员与静态函数//继承实现代码重用/单继承QT案例/多继承简介以及实战/Gpu编程

类型转换函数与构造转换函数 #include<iostream> class fushu { public: explicit fushu(int num)//避免隐式转换,引发歧义 { x = num; y = num; } void print() { std::cout << x << "+" << y << "i" << std::endl; } operator int(); //不支