Qt之Threads和QObjects
简述
QThread继承自QObject,它发射信号(signals)以表明线程执行开始或结束,并提供了一些槽函数(slots)。
更有趣的是,QObjects可以在多线程中使用,发射信号以在其它线程中调用槽函数,并且向“存活”于其它线程中的对象发送事件(post events)。这是可能的,因为每一个线程都拥有它自身的事件循环(event loop)。
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进行异步通信。
Qt之Threads和QObjects的更多相关文章
- Threads Events QObjects
Events and the event loop Being an event-driven toolkit, events and event delivery play a central ro ...
- Qt之QThread(深入理解)
简述 为了让程序尽快响应用户操作,在开发应用程序时经常会使用到线程.对于耗时操作如果不使用线程,UI界面将会长时间处于停滞状态,这种情况是用户非常不愿意看到的,我们可以用线程来解决这个问题. 前面,已 ...
- 《Qt 实战一二三》
简介 "我们来自Qt分享&&交流,我们来自Qt Quick分享&&交流",不管你是笑了,还是笑了,反正我们是认真的.我们就是要找寻一种Hold不住的 ...
- event & signals & threads
The Event Systemhttp://doc.qt.io/qt-4.8/eventsandfilters.html Each thread can have its own event loo ...
- 使用 Qt 获取 UDP 数据并显示成图片(2)
本文首发于 BriFuture 的 个人博客 在我的前一篇文章 使用 Qt 获取 UDP 数据并显示成图片 中,我讲了如何用 Python 模拟发送数据,如何在 Qt 中高效的接收 UDP 数据包并将 ...
- QThread 与 QObject的关系
Threads and QObjects QThread 继承 QObject..它可以发送started和finished信号,也提供了一些slot函数. QObject.可以用于多线程,可以发送信 ...
- 很多人以为 connect 和 disconnect 应该像 new 和 delete 一样成对出现 这是错误的(只要 sender 或 receiver 其中之一不存在了,connect 会自动失效。QObject::connect 函数是线程安全的)
其实我写文章也是边查资料边编辑的 有时候是怕自己的阐述不严谨,有时候是怕自己重复造轮子 就像有些人不停的教大家QLabel QDialog QWidget 个人是不屑的 命令模式 用 Qt's Und ...
- Qt经典—线程、事件与Qobject(耳目一新)
介绍 You’re doing it wrong. — Bradley T. Hughes 线程是qt channel里最流行的讨论话题之一.许多人加入了讨论并询问如何解决他们在运行跨线程编程时所遇到 ...
- 【转】Qt事件循环与线程 二
转自:http://blog.csdn.net/changsheng230/article/details/6153449 续上文:http://blog.csdn.net/changsheng230 ...
随机推荐
- 电量检测芯片BQ27510使用心得
最近接触到一款TI的电量检测芯片BQ27510,网上很少有人提及该芯片如何使用,大部分博文都是搬得BQ27510的datasheet,至于真正使用过的很少,该芯片我个人感觉还是非常强大的,能自动学习你 ...
- 去处HTML标签
JavaScript去处HTML标签 function removeHTMLTag(str) { str = str.replace(/<\/?[^>]*>/g, ''); //去除 ...
- getbyclass
其实以前我偷偷学习正则表达式的时候,写过一个getbyclass的方法,最近翻了翻到处都是错,或者好多重复的,没有用的 代码,于是显得没事我就把这个精简了一下,其实这个方法现在我觉得也是有问题的,问题 ...
- OB命令大全
CALC : 判断表达式 WATCH : 添加监视表达式 AT : 在指定地址进行反汇编 FOLLOW : 跟随命令 ORIG : ...
- 数据词典与ABAP类型映射
声明:原创作品,转载时请注明文章来自SAP师太技术博客( 博/客/园www.cnblogs.com):www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将 ...
- POJ 3461 Oulipo(乌力波)
POJ 3461 Oulipo(乌力波) Time Limit: 1000MS Memory Limit: 65536K [Description] [题目描述] The French autho ...
- 测算Redis处理实际生产请求的QPS/TPS
测算Redis处理实际生产请求的QPS/TPS Benchmark工具 redis发布版本中自带了redis-benchmark性能测试工具; 示例: 使用50个并发连接,发出100000个请求,每个 ...
- VirtualBox全屏切换
用VirtualBox的时候,如果设置为全屏,想再切换回来,没有什么菜单,只有通过键盘的快捷键来操作,才可以恢复. 我常常忘掉,所以老是得去找,以后需要记住这几个按键的快捷键. 1.全屏与非全屏切换: ...
- iOS - Photo Album 图片/相册管理
前言 NS_CLASS_AVAILABLE_IOS(2_0) @interface UIImagePickerController : UINavigationController <NSCod ...
- 从网页(WEB)登录SAP
以下这篇文章写得很详细,照着做就可以了: http://www.doc88.com/p-293361232332.html 设好后, 默认的端口是80$$, 其中$$是安装SAP时的instanc ...