线程和QObjects

原文:http://doc.qt.io/qt-5/threads-qobject.html


线程 和 QObjects

QThread继承了QObject. 它发射singlas以表明该线程开始或结束了执行,并提供少量slots. 

更有趣的是,QObjects可以被使用在多线程中,发射信号以在其他线程中调用slots,并且向存在于其他线程中的对象发送events. 这是可能的,因为每一个线程都拥有它自身的event loop. 


QObject可重入性

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

  • 一个QObject类型的孩子必须总是被创建在它的父亲所被创建的线程中。这意味着,在其他事情中,永远不要把QThread对象(this指针)作为在该线程中创建的一个对象的父亲(因为QThread对象自己是被创建在另外一个线程中)。
  • 事件驱动的对象可能只能被用在一个线程中。特别的,这适用于计时器机制(timer mechanism)和网络模块(network module)。比如,你不能在不属于这个对象的线程中启动一个定时器或连接一个socket. 
  • 必须保证在删除QThread之前删除所有创建在这个thread中的对象。在run()函数的实现中,通过在栈中创建这些对象,可以轻松地做到这一点。

虽然QObject是可重入的,但GUI类,尤其是QWidget和它的所有子类都不是可重入的。它们只能被用在主线程中。如前面所述,QCoreAppliction::exec()必须也从那个线程被调用。

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

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


基于线程的Event Loop

每个线程都有它自己的event loop. 初始线程通过QCoreApplication::exec()来启动它自己的event loop, 或者对于单对话框的GUI应用,有些时候用QDialog::exec()来启动event loop. 其他的线程可以用QThread::exec()来启动event loop. 类似于QCoreApplication,QThread提供一个exit(int)函数和一个叫quit()的slot. 

具有event loop的线程使得该线程可以利用一些非GUI的、要求有event loop存在的Qt类(比如QTimer,QTcpSocket,和QProcess)。它也使得将来自于一些线程的signals连接到一个特定线程的slots上成为可能。这一点将会在下面的"Signals and Slots Across Threads"有详细介绍。


一个QObject实例被称之为存在于它所被创建的线程中。关于这个object的events被分发到该线程的event loop中。可以用QObject::thread()方法取得一个QObject所存在的线程。

QObject::moveToThread()方法改变一个对象和它的孩子们的线程所属性。(该对象不能被移动到其他线程,如果它有父亲的话)。

从另一个线程而不是该QObject对象所属的线程来对该QObject对象调用delete方法是不安全的,除非你能保证该对象在该时刻不处理events. 使用QObejct::deleteLater()更好。一个DeferredDelete的event将被提交,而该对象的event loop最终会处理这个event. 默认情况下,拥有一个QObject的线程就是创建它的那个线程,但QObject::moveToThread()会改变对象的所属线程。

如果没有event loop在运行的话,events是不会被对象处理的。比如,你在一个线程中创建了一个QTimer对象,但从没有调用exec()方法,那么该QTimer就永远不会发射它的timeout()的signal. 即使调用deleteLater()也不行。(这些限制也同样适用于主线程)。
利用线程安全的方法QCoreApplication::postEvent(),你可以在任何时刻给任何线程中的任何对象发送events. 这些事件将自动被分发到该对象所被创建的线程的event loop中。

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


从不同的线程访问QObject的子类

QObject和它所有的子类不是线程安全的。这包含了整个event delivery系统。要注意的是,当你从另一个线程访问一个对象时,event loop可能正在向你的这个QObject的子类发送event. 

如果你正在调用一个QObject子类的函数,而该子类对象并不存在于当前线程中,并且该对象是可以接收事件的,那么你必须用互斥机制保护对该QObject子类的内部数据的所有访问;否则,就有可能发生崩溃和非预期的行为。
同其他对象一样,QThread对象所存在的线程就是该对象被创建时所在的线程 -- 而未必是在QThread::run()被调用时所在的线程。一般来说在QThread子类中提供slots是不安全的,除非你用互斥量保护了成员变量。

从另一方面来说,从QThread::run()的实现中来发射signals是安全的,因为Signal发射是线程安全的。


跨线程的Signals和Slots

Qt支持如下的signal-slot连接类型:

  • Auto Connection(默认) :如果发射signal和接收的对象在同一个线程,那么行为等同于Direct Connection;否则,行为等同于Queued Connection. 
  • Direct Connection:当signal被发射后,slot立即被调用。slot在signal发射者所在的线程中被执行,而未必需要在接收者的线程中。
  • Queued Connection:当控制回到接收者的event loop后,slot才被调用。slot被执行在接收者的线程中。
  • Blocking Queued Connection:slot的调用情形和Queued Connection相同,不同的是当前的线程会阻塞住,直到slot执行结束返回。注意,在同一个线程中使用这种类型进行连接会导致死锁。
  • Unique Connection:行为与Auto Connection相同,但是连接只会在“不会与已存在的连接相同”时建立,即:如果相同的signal已经被连接到相同的slot,那么连接就不会被再次建立,而connect()函数会返回false. 

通过传递一个参数给connect()函数来指定连接类型。要知道的是,如果event loop运行在接收者的线程中,而sender和receiver在不同的线程时,使用Direct Connection是不安全的。同样的原因,调用存在于另一个线程中的对象的函数是不安全的。

QObject::coneect()本身是线程安全的。
Mandelbrot Example使用了Queued Connection来连接一个worker线程和主线程。为了避免冻结主线程的event loop(即避免因此而冻结了应用的UI),所有Mandelbrot的各种计算都是在一个worker线程中完成的。该线程结束一个计算时发射一个信号。
类似的, Blocking Fortune Client Example使用了一个线程来和TCP server进行异步通信。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值