现在的位置: 首页 > 自动控制 > 工业·编程 > 正文

QT4 thread和QObject关系详解

2015-01-20 06:39 工业·编程 ⁄ 共 2450字 ⁄ 字号 暂无评论

QThread从QObject派生。它发出信号来表明线程开始了或结束了。并且也提供了几个槽。

更有趣的是,QObject可以在多个程中同时使用,可以发出信号给另外线程的槽,以及向“活在”另外线程中的对象邮寄事件。以上之所以能发生,是因为每个初程都被允许拥有它自己的事件循环。

QObject 重入

QObject是可重入的。它大多数非界面派生类,比如QTimer,QTcpSocket,QFtp,和QProcess,也都是可重入的,使得在多个线程中同时使用这些类成为可能。但是注意这些类被设计为在一个线程中创建和使用;在一个线程中创建一个对象然后在另一个线程中调用它的方法是不能保证一定能工作的。有三个限制条件要注意:

QObject的儿子必须在创建它爹的线程中创建。这表示,你永远不能将QThread对象(this)作为parent传给在此thread创建的对象,因为线程对象自己就是在另一个线程中创建的。

事件驱动的对象应该只用于一个线程中。这一条尤其应用于定时器和网络模块。比如,你不能在创建对象之外的线程中启动一个定时器或连接一个socket。

你必须保证在线程中创建的一切对象在QThread被删除之前被删除。这可以通过在你的run()实现中在棧中创建对象来轻松搞定。

尽管QObject是可重入的,但GUI类,尤其是QWidget和它所有的派生类们,都不是可重入的。它们只能在主线程中使用。QCoreApplication::exec()必须在这个线程中调用。

在实际应用中,最好的方式是把耗时的计算放到主线程中外进行,完成后通知主线程显示结果。

Pre-Thread Event循环

每个线程都可以有它自己的事件循环。初始的线程使用QCoreApplication::exec()来开始它的事件循环;其它的线程可以使用QThread::exec()来启动循环。就像QCoreApplication,QThread也提供了一个exit(int)方法和一个quit()槽。

线程中的事件循环使得在线程中使用依靠消息循环的非GUI的QT类成为可能(比如QTimer,QTcpSocket,QProcess)。它也使得从任何线程连接信号到一个线程的槽成为可能。这在下面的“信号和槽穿越线程”一节中有详细解释。

一个QObject实例在那个线程中创建,就叫做“活”在那个线程中。给这个对象的事件们通过线程的事件循环派发。一个QObject对象所“活在”的线程通过QObject::thread()可以取得。

注意在QApplication之前创建的QObject调用QObject::thread()会返回0.这意味着主线程将只为这些对象处理邮寄的事件;对于没有线程的对象,其它的事件处理跟本不会发生。使用QObject::moveToThread()方法来改变对象(和它儿子们)的线程(如果一个对象有爹,它就不能被移动到另外线程)。

在拥有对象之外的线程中调用删除对象是不安全的,除非你能保证在被删除时不在处理事件。但可以使用QObject::deleteLater(),它会寄出DeferedDelete事件,对象的线程的事件循环最终会抓住它。默认下,拥有Qobject的线程就是创建QObject的线程,但在QObject::moveToTread()之后就变了。

如果没有事件循环,事件将不能传给对象。比如,如果你在一个线程中创建一个QTimer对象,但是没有再调用exec(),那么QTimer将永不能触发timeout()信号。deleteLater()也不再能工作。(这些也同样适用于主线程。)

你可以在任何线程中使用QCoreApp:postEvent()手动向任何对象邮寄事件。事件将被对象所在线程的事件循环自动派发。

事件过滤器被所有的线程所支持,但有个限制条件:监视对象必须与被监视对象位于同一个线程中。同样的,QCoreApplication::sendEvent()(不同于 QCoreApplication::postEvent())只能在同一线程中的对象之间发送事件。

从另外线程访问QObject子类

QObject和它所有的子类都不是线程安全的,这也包含整个事件派送系统。要记住,当你从另外线程访问对象时,事件循环可能派送事件到你的QObject子类。

如果你调用一个非本线程的QObject的子类的函数并且这个对象可能接收事件,你必须用mutex保护所有对你的QObject子类的内部数据的访问;否则,你可能体验的什么叫崩溃。

就像其它对象,QThread对象“活”在创建它的线程中,而不是它自己所代表的线程中。通常在你的QThread子类中提供槽是不安全的,除非你用mutex保护成员变量。

另一方面,你可以从你的QThread tun()中安全的发出信号,因为信号发射是线程安全的。

穿越线程的信号和槽们

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

自动连接(默认)- 如果信号是从接收对象所在的线程发出的,其行为与“直接连接”相同。否则,其行为与“队列连接”相同。

直接连接- 当信号发出,槽会被立马调用。此槽在发出者的线程中执行,而不一定是接收者所在的线程。

队列连接- 当控制返回到接收者所在线程的事件循环时调用。槽在接收者的线程中执行。

阻塞的队列连接- 槽像“队列连接”那样被调用,除了一点:当前线程会阻塞住直到槽返回。注:在同一线程中使用此类型的连接将导致死锁!

唯一连接- 行为与“自动连接”相同,但连接必须在无复制品时才能建立。也就是,如果在相同的两个对象之间已经建立了同一个信号到同一个槽的连接,那么连接就不能建立,connect()返回false。

连接类型可以通过给connect()传递一个额外的参数来指定。注意当接收者和发送者位于不同的线程中时,使用“直接连接”,如果事件循环是运行于接收者的线程中,此时是不安全的,同理调用位于另外线程的对象的任何函数都是不安全的。

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

给我留言

留言无头像?