QObjects和多线程

QThread派生自QObject。它发射信号来指示新线程的started 和 finished,也提供一些槽。

有意思的是,QObjects可以用在多线程里,发射信号来调用另一个线程的槽,把事件投递到另一个线程的对象上。这是可能的,因为每个线程都可以有自己的事件循环(event loop)。

QObject及其子类都是线程非安全的。最好在一个线程里操作。

自己定义的类可以多线程使用,但必须用mutex保护好数据。

QObject的可重入

QObject是可重入的(GUI子类不可重入)。其大多数的非GUI子类,如QTimer,QTcpSocket,QUdpSocket和QProcess等都是可重入的,可以在多个线程了使用这些类。但是,这些类被设计成在单个线程里创建和使用;在一个线程里创建对象,在另一个线程里调用其成员函数,其工作正确性是无法保证的(not guaranteed to work)。

个人理解:类可重入,表示可以在多个线程里创建类的对象(例如,在thread-A和thread-B中分别创建一个QTimer对象,没问题)。但这不表示对象是线程安全的(其实QObject是线程不安全的)。

需要注意以下三种约束:

1.  QObject的子对象必须在父对象所在的线程里创建。

2. 事件驱动型的对象(event driven objects)只能在单线程里使用。特别的,这适用于timer和network模块。例如,不能在其他线程(非亲和线程)里启动timer或连接socket。加锁也不行。

3. 必须保证在QThread销毁前删除所有在其线程里创建的对象。这个好办,把对象创建在Qthread::run()的栈上即可。

尽管QObject是可重入的,但是GUI类,尤其是QWidget及其子类都是不可重入的。他们只能在主线程里使用。而且QCoreApplication::exec()也只能在主线程里运行。

在QApplication之前创建QObject对象是不安全的,可能在exit是导致程序崩溃。这意味着,不能创建QObject的静态实例。  QApplication对象必须是第一个创建、最后一个销毁的QObject对象。

每个线程都有事件循环(Event Loop)

每个线程都有它的事件循环。主线程使用QCoreApplication::exec()开始事件循环。QThread提供了exit(int)函数和quit()槽。

线程有了事件循环,就可以包含那些需要事件循环的Qt子类(如QTimer,QTcpSocket和QProcess等);也让多线程之间的信号-槽连接变得可能。

QObjects和多线程_第1张图片
事件循环

一个QObject被认为存在于创建它的线程中。该对象的事件被它所在线程的事件循环来分发。QObject所在的线程可以用QObject::thread()函数获取。

可以用QObject::moveToThread改变一个对象(及其子对象)的所在线程。

从另外一个而线程里调用QObject的delete函数(或访问此对象)是不安全的,除非你确定此时此刻对象没有正在处理时间(the object isn't processing events at that moment)。应该使用QObject::deleteLater(),发送DeferredDelete事件后,对象所在线程的消息循环会处理事件。

如果所在线程没有运行消息循环,事件就不会投递到对象上。例如在次线程里创建了QTimer对象但没有运行exec()函数,则QTimer永远都不会发射timeout()信号。

我们可以从任意线程里手动投递事件到一个对象上,使用QCoreApplication::postEvent()函数。对象所在的线程会在事件循环里分发事件。

从其他线程里访问QObject子类

QObject和其所有子类都是线程非安全的。需要特别注意的是,当你在其他线程里访问对象时,所属线程的事件循环可能正在向对象投递事件。

如果在一个线程里调动其他线程QObect的函数且该对象可以接收事件,则必须用mutex来保护其内部数据,否则可能会出错。(个人理解,使用QTcpSocket等对象时,由于内部数据我们无法访问,也就无法用mutex保护,导致这些对象只能在单线程里操作。我的理解是正确的)。

QThread对象属于创建它的线程,而不是QThread::run()线程。因此在QThread的子类中设置的槽函数在run()里调用通常是不安全的,除非用mutex保护被访问的数据。

信号的发射是线程安全的,因此在QThread::run()中发射信号是安全的。

跨线程的信号和槽

信号和槽的链接类型包括Auto Connection,Direct Connection,Queued Connection,Blocking Queued Connection和Unique Connection。

在“Signals和Slots”中对上述链接类型进行了说明。注意,当sender和receiver不在同一个线程中时,如果receiver的线程在运行事件循环(if an event loop is running in the receiver's thread),则使用direct connection是不安全的(应该用queued connection)。同样的原因,调用其他线程里的对象的函数也是不安全的。

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

多线程里用queued connnection进行通信。主线程管理GUI,次线程做运算,然后发射信号来通知主线程更新GUI。

通常用一个单独的线程处理TCP通信。



参考Qt官方文档:"Threads and QObject".

QT多线程深入分析

你可能感兴趣的:(QObjects和多线程)