线程的事件循环:
- 每一个线程都可以有它自己的事件循环。初始化线程(GUI线程)使用QCoreApplication::exec()来开启它的事件循环(对于独立的对话框界面程序,也可以使用QDialog::exec()).其他的线程可以使用QThread::exec()来开启一个事件循环。与QCoreApplication相似,QThread提供了一个exit()函数和一个quit()槽。在一个线程中使用事件循环,使得该线程可以使用那些需要事件循环的非GUI类(例如,QTimer、QTcpSocket、QProcess等等)。也使得该线程可以关联任意一个线程的信号到一个指定线程的槽。
QObject与事件循环
- 如果在一个线程中创建了一个QObject对象,那么这个QObject对象被称为居住在该线程(live in the thread)。发往这个对象的事件由该线程的事件循环进行分派。可以使用QObject::thread()获得该对象所居住的线程。可以使用QObject::moveToThread()来改变对象及其孩子所在的线程(如果该对象有父对象,那么它无法被移动)。
- 在其他线程(不是拥有该QObject对象的线程)中调用delete来删除(或通过其他方式访问)该QObject对象是不安全的,除非可以确保该对象当前没有在处理事件。可以使用QObject::deleteLater()来代替,这样会发送一个DeferredDelete事件,最终该对象所在线程的事件循环将会获取该事件。默认的,拥有该QObject对象的线程就是创建该QObject对象的线程,只要没有调用过QObject::moveToThread()函数。
- 如果没有运行事件循环,则事件将不会传送到对象。例如,如果在一个线程中创建了一个QTimer对象,但是从来没有调用exec()函数,那么QTimer将永远不会发射它的timeout()信号。调用deleteLater()也不会工作。
从其他线程中访问QObject子类
- QObject和它所有的子类都不是线程安全的,这包括了整个事件传递系统。需要注意的是,正在从其他线程中访问一个对象时,事件循环可能正在向这个对象传递事件。
- 如果调用一个没有在当前线程中的QObject子类的函数,而这个对象有可能会获取事件,那么就必须使用mutex来保护对这个QObject子类的内部数据的所有访问。否则,可能出现崩溃或者其他意外行为。
- 与其他对象相似,QThread对象居住于创建该对象的线程。在QThread子类中提供槽一般是不安全的,除非使用mutex来保护乘员变量。但是,可以安全地在QThread::run()函数中发射信号,因为信号发射是线程安全的。
跨线程的信号和槽
Qt支持几种信号和槽关联类型:
- Auto Connection(默认)。如果信号发射和信号接收的对象在同一个线程,那么执行方式与Direct Connection相同。否则,执行方式与Queued Conection相同。
- Direct Connection。信号发射后,槽立即被调用。槽在信号发送者的线程中执行,而接收者并非必须在同一个线程。
- Blocking Queued Connection。槽的调用与Queued Connection相同,不同的是当前线程会阻塞直到槽返回。注意:使用这种方式关联在相同线程中的对象时,会引起死锁。
- Unique Connection。执行方式与Auto Connection相同。只不过关联是唯一的。
可以通过向connect()函数传递附加的参数来指定关联类型。注意,如果在接收者线程中有一个事件循环,那么当发送者与接收者在不同的线程中时,使用Direct Connection是不安全的;类似的,调用其他线程中的对象的任何函数也是不安全的,不过,需要明确的是QObject::connect()函数本身是线程安全的。