QT thread学习1
QRunnable Class
QRunnable类是所有可运行对象的基类。
QRunnable类是一个接口,用于表示需要执行的任务或代码段,由run()函数的重新实现表示。
您可以使用QThreadPool在单独的线程中执行代码。 如果autoDelete()返回true(默认值),QThreadPool将自动删除QRunnable。 使用setAutoDelete()来更改自动删除标志。
QThreadPool支持通过在run()函数中调用QThreadPool::tryStart(this)来多次执行同一个QRunnable。 如果autoDelete被启用,QRunnable将在最后一个线程退出run函数时被删除。 当启用autoDelete时,多次使用同一个QRunnable调用QThreadPool::start()会创建一个竞争条件,不建议使用。
QThreadPool Class
QThreadPool类管理QThreads的集合。
QThreadPool管理和回收单个QThread对象,以帮助减少使用线程的程序中的线程创建成本。 每个Qt应用程序都有一个全局QThreadPool对象,可以通过调用globalInstance()来访问。
要使用其中一个QThreadPool线程,请继承QRunnable并实现run()虚函数。 然后创建该类的一个对象,并将其传递给QThreadPool::start()。
class HelloWorldTask : public QRunnable { void run() { qDebug() << "Hello world from thread" << QThread::currentThread(); } }; HelloWorldTask *hello = new HelloWorldTask(); // QThreadPool takes ownership and deletes 'hello' automatically QThreadPool::globalInstance()->start(hello);
默认情况下,QThreadPool会自动删除QRunnable。 使用QRunnable::setAutoDelete()来更改自动删除标志。
QThreadPool支持通过在QRunnable::run()中调用tryStart(this)来多次执行同一个QRunnable。 如果autoDelete被启用,QRunnable将在最后一个线程退出run函数时被删除。 当启用autoDelete时,使用相同的QRunnable多次调用start()会创建一个竞争条件,不建议这样做。
在一定时间内未使用的线程将过期。 缺省的超时时间为30000毫秒(30秒)。 这可以使用setExpiryTimeout()来更改。 设置负过期超时将禁用过期机制。
调用maxThreadCount()来查询要使用的最大线程数。 如果需要,您可以使用setMaxThreadCount()更改限制。 默认的maxThreadCount()是QThread::idealThreadCount()。 函数的作用是:返回当前正在工作的线程数。
reserveThread() 函数的作用是:为外部使用保留一个线程。 当你完成线程时,使用releaseThread(),这样它可以被重用。 本质上,这些函数临时增加或减少活动线程数,在实现QThreadPool不可见的耗时操作时非常有用。
请注意,QThreadPool是一个用于管理线程的低级类,请参阅Qt Concurrent模块了解更高级别的替代方案。
class Q_CORE_EXPORT QThreadPool : public QObject { Q_OBJECT Q_DECLARE_PRIVATE(QThreadPool) Q_PROPERTY(int expiryTimeout READ expiryTimeout WRITE setExpiryTimeout) Q_PROPERTY(int maxThreadCount READ maxThreadCount WRITE setMaxThreadCount) Q_PROPERTY(int activeThreadCount READ activeThreadCount) friend class QFutureInterfaceBase; public: QThreadPool(QObject *parent = Q_NULLPTR); ~QThreadPool(); static QThreadPool *globalInstance(); void start(QRunnable *runnable, int priority = 0); bool tryStart(QRunnable *runnable); int expiryTimeout() const; void setExpiryTimeout(int expiryTimeout); int maxThreadCount() const; void setMaxThreadCount(int maxThreadCount); int activeThreadCount() const; void reserveThread(); void releaseThread(); bool waitForDone(int msecs = -1); void clear(); Q_REQUIRED_RESULT bool tryTake(QRunnable *runnable); };
QThread Class
QThread类提供了一种平台无关的方式来管理线程。
QThread对象管理程序中的一个控制线程。 QThreads在run()中开始执行。 默认情况下,run()通过调用exec()启动事件循环,并在线程中运行Qt事件循环。
你可以通过使用QObject::moveToThread()将工作对象移动到线程中。
class Worker : public QObject { Q_OBJECT public slots: void doWork(const QString ¶meter) { QString result; /* ... here is the expensive or blocking operation ... */ emit resultReady(result); } signals: void resultReady(const QString &result); }; class Controller : public QObject { Q_OBJECT QThread workerThread; public: Controller() { Worker *worker = new Worker; worker->moveToThread(&workerThread); connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater); connect(this, &Controller::operate, worker, &Worker::doWork); connect(worker, &Worker::resultReady, this, &Controller::handleResults); workerThread.start(); } ~Controller() { workerThread.quit(); workerThread.wait(); } public slots: void handleResults(const QString &); signals: void operate(const QString &); };
Worker槽中的代码将在一个单独的线程中执行。然而,你可以自由地将Worker的插槽连接到任何线程中的任何对象的任何信号。由于一种称为队列连接的机制,在不同的线程之间连接信号和插槽是安全的。
让代码在单独的线程中运行的另一种方法是子类QThread并重新实现run()。例如:
class WorkerThread : public QThread { Q_OBJECT void run() override { QString result; /* ... here is the expensive or blocking operation ... */ emit resultReady(result); } signals: void resultReady(const QString &s); }; void MyObject::startWorkInAThread() { WorkerThread *workerThread = new WorkerThread(this); connect(workerThread, &WorkerThread::resultReady, this, &MyObject::handleResults); connect(workerThread, &WorkerThread::finished, workerThread, &QObject::deleteLater); workerThread->start(); }
在该示例中,线程将在run函数返回后退出。除非调用exec(),否则不会有任何事件循环在线程中运行 ???。
重要的是要记住,QThread实例存在于实例化它的旧线程中,而不是在调用run()的新线程中。这意味着QThread的所有排队槽都将在旧线程中执行。因此,希望在新线程中调用插槽的开发人员必须使用工作对象方法;新的插槽不应该直接实现到子类QThread中。
当子类化QThread时,请记住,构造函数在旧线程中执行,而run()在新线程中执行。如果两个函数都访问一个成员变量,那么该变量将从两个不同的线程访问。检查这样做是否安全。
Managing Threads
当线程start()和finish()时,QThread会通过一个信号通知你,或者你可以使用isFinished()和isRunning()来查询线程的状态。
你可以通过调用exit()或quit()来停止线程。在极端情况下,您可能希望强制terminate()一个正在执行的线程。然而,这样做是危险的,也是不被鼓励的。有关详细信息,请阅读文档中的terminate()和setTerminationEnabled()。
从Qt 4.8开始,通过将finished()信号连接到QObject::deleteLater(),可以释放刚刚结束的线程中的对象。
使用wait()来阻塞调用线程,直到另一个线程完成执行(或经过指定的时间)。
QThread还提供了静态的、平台无关的睡眠函数:sleep()、msleep()和ussleep()分别允许秒、毫秒和微秒分辨率。这些函数在Qt 5.0中公开。
注意:wait()和sleep()函数通常是不必要的,因为Qt是一个事件驱动的框架。考虑侦听finished()信号,而不是wait()。考虑使用QTimer而不是sleep()函数。
static currentThreadId()和currentThread()返回当前执行线程的标识符。前者返回线程的平台特定ID;后者返回一个QThread指针。
要选择线程的名称(例如,在Linux上通过命令ps -L标识),可以在启动线程之前调用setObjectName()。如果您没有调用setObjectName(),那么给线程的名称将是线程对象运行时类型的类名(例如,Mandelbrot示例中的“RenderThread”,因为它是QThread子类的名称)。请注意,这在Windows上的版本构建中目前是不可用的。
[slot] void QThread::terminate()
终止线程的执行。线程可能会立即终止,也可能不会,这取决于操作系统的调度策略。在terminate()之后使用QThread::wait()。
当线程终止时,所有等待该线程完成的线程将被唤醒。
警告:此函数是危险的,不鼓励使用。线程可以在其代码路径的任何点终止。线程可以在修改数据时终止。线程没有机会在它自己之后进行清理,解锁任何持有的互斥对象,等等。简而言之,仅在绝对必要时使用此函数。
可以通过调用QThread::setTerminationEnabled()显式地启用或禁用终止。在终止被禁用时调用此函数将导致终止被延迟,直到重新启用终止。
[static protected] void QThread::setTerminationEnabled(bool enabled = true)
基于enabled参数启用或禁用当前线程的终止。该线程必须由QThread启动。
当enabled为false时,终止被禁用。对QThread::terminate()的后续调用将立即返回而不生效。相反,终止被推迟到启用终止。
当enabled为true时,表示启用终止。以后调用QThread::terminate()将正常终止线程。如果终止被延迟(即QThread::terminate()在终止被禁用的情况下被调用),这个函数将立即终止调用线程。注意,在这种情况下,这个函数不会返回。
class Q_CORE_EXPORT QThread : public QObject { Q_OBJECT public: static Qt::HANDLE currentThreadId() Q_DECL_NOTHROW Q_DECL_PURE_FUNCTION; static QThread *currentThread(); static int idealThreadCount() Q_DECL_NOTHROW; static void yieldCurrentThread(); explicit QThread(QObject *parent = Q_NULLPTR); ~QThread(); enum Priority { IdlePriority, LowestPriority, LowPriority, NormalPriority, HighPriority, HighestPriority, TimeCriticalPriority, InheritPriority }; void setPriority(Priority priority); Priority priority() const; bool isFinished() const; bool isRunning() const; void requestInterruption(); bool isInterruptionRequested() const; void setStackSize(uint stackSize); uint stackSize() const; void exit(int retcode = 0); QAbstractEventDispatcher *eventDispatcher() const; void setEventDispatcher(QAbstractEventDispatcher *eventDispatcher); bool event(QEvent *event) Q_DECL_OVERRIDE; int loopLevel() const; public Q_SLOTS: void start(Priority = InheritPriority); void terminate(); void quit(); public: // default argument causes thread to block indefinetely bool wait(unsigned long time = ULONG_MAX); static void sleep(unsigned long); static void msleep(unsigned long); static void usleep(unsigned long); Q_SIGNALS: void started(QPrivateSignal); void finished(QPrivateSignal); protected: virtual void run(); int exec(); static void setTerminationEnabled(bool enabled = true); protected: QThread(QThreadPrivate &dd, QObject *parent = Q_NULLPTR); private: Q_DECLARE_PRIVATE(QThread) friend class QCoreApplication; friend class QThreadData; };
Threads and QObjects
QThread继承QObject。它发出信号来指示线程开始或结束执行,并提供一些槽位。
更有趣的是,QObjects可以在多个线程中使用,发出调用其他线程中的槽的信号,并将事件发送给其他线程中“活动”的对象。这是可能的,因为每个线程都允许有自己的事件循环。
QObject Reentrancy
QObject是可重入的。它的大多数非gui子类,如QTimer, QTcpSocket, QUdpSocket和QProcess,也是可重入的,这使得同时在多个线程中使用这些类成为可能。请注意,这些类被设计为在单个线程中创建和使用;在一个线程中创建一个对象,然后从另一个线程调用它的函数,这并不保证能够正常工作。有三个约束条件需要注意:
- QObject的子对象必须总是在创建父对象的线程中创建。这意味着,除其他事项外,您不应该将QThread对象(This)作为在线程中创建的对象的父对象传递(因为QThread对象本身是在另一个线程中创建的)。
- 事件驱动对象只能在单个线程中使用。具体来说,这适用于定时器机制和网络模块。例如,您不能在非对象的线程中启动计时器或连接套接字。
- 您必须确保在删除QThread之前删除线程中创建的所有对象。这可以通过在run()实现中在堆栈上创建对象来轻松完成。
虽然QObject是可重入的,但GUI类,尤其是QWidget及其所有子类,是不可重入的。它们只能在主线程中使用。如前所述,QCoreApplication::exec()也必须从该线程调用。
实际上,在主线程以外的其他线程中使用GUI类是不可能的,通过将耗时的操作放在单独的工作线程中,并在工作线程完成时在主线程的屏幕上显示结果,可以很容易地解决这一问题。这是用于实现Mandelbrot示例和Blocking Fortune Client示例的方法。
一般来说,在QApplication之前创建QObjects是不支持的,并且可能在退出时导致奇怪的崩溃,这取决于平台。这意味着QObject的静态实例也不受支持。一个合理结构的单线程或多线程应用程序应该使QApplication成为第一个创建的,最后一个销毁的QObject。
Per-Thread Event Loop
每个线程都可以有自己的事件循环。初始线程使用QCoreApplication::exec()启动它的事件循环,或者对于单对话框GUI应用程序,有时使用QDialog::exec()。其他线程可以使用QThread::exec()启动事件循环。和QCoreApplication一样,QThread提供了一个exit(int)函数和一个quit()槽。
线程中的事件循环使线程能够使用某些非gui Qt类,这些类需要存在事件循环(如QTimer、QTcpSocket和QProcess)。它还可以将来自任何线程的信号连接到特定线程的槽。这将在下面的信号和跨线程槽一节中进行更详细的解释。
QObject实例被认为存在于创建它的线程中。该对象的事件由该线程的事件循环分派。QObject所在的线程可以使用QObject::thread()。
QObject::moveToThread()函数改变一个对象及其子对象的线程关联(如果对象有父对象,则不能移动它)。
在QObject上从非拥有该对象的线程(或以其他方式访问该对象)调用delete是不安全的,除非您保证该对象在那个时刻没有处理事件。而使用QObject::deleteLater(),将会发布一个DeferredDelete事件,对象线程的事件循环最终会拾取该事件。默认情况下,拥有QObject的线程是创建QObject的线程,但不是在调用了QObject::moveToThread()之后。
如果没有运行事件循环,事件将不会被交付给对象。例如,如果你在一个线程中创建了一个QTimer对象,但是从来没有调用exec(), QTimer将永远不会发出它的timeout()信号。调用deleteLater()也不起作用。(这些限制也适用于主线程。)
你可以使用线程安全的函数QCoreApplication::postEvent()在任何时间、任何线程中手动发送事件到任何对象。事件将由创建对象的线程的事件循环自动调度。
所有线程都支持事件过滤器,但限制是监视对象必须与被监视对象位于同一线程中。类似地,QCoreApplication::sendEvent()(与postEvent()不同)只能用于将事件分派给调用该函数的线程中的对象。
Accessing QObject Subclasses from Other Threads 从其他线程访问QObject子类
QObject及其所有子类都不是线程安全的。这包括整个事件交付系统。重要的是要记住,当您从另一个线程访问对象时,事件循环可能会将事件传递给您的QObject子类。
如果你在一个QObject子类上调用一个不存在于当前线程中的函数,并且该对象可能接收到事件,你必须用互斥锁保护所有访问QObject子类内部数据的权限;否则,您可能会遇到崩溃或其他不希望出现的行为。
与其他对象一样,QThread对象位于创建对象的线程中——而不是在调用QThread::run()时创建的线程中。在QThread子类中提供插槽通常是不安全的,除非使用互斥保护成员变量。
另一方面,您可以安全地从QThread::run()实现发出信号,因为信号发出是线程安全的。
Signals and Slots Across Threads 线程间的信号和槽
Qt支持以下信号槽连接类型:
- Auto Connection(默认)如果信号是在接收对象同一线程中发出的,那么行为与直接连接相同。否则,行为与排队连接相同。”
- Direct Connection 当信号发出时,插槽立即被调用。槽函数在发射的线程中执行,这不一定是接收方的线程。
- Queued Connection 当控制返回到接收方线程的事件循环时,将调用插槽。插槽在接收方的线程中执行。
- Blocking QueuedConnection 插槽被调用作为排队连接,除了当前线程块,直到插槽返回。
注意:使用此类型连接同一线程中的对象将导致死锁。
- Unique Connection 与自动连接相同,但只有在不复制现有连接的情况下才会建立连接。也就是说,如果相同的信号已经连接到同一对对象的同一插槽,那么连接不会建立,并且connect()返回false。
连接类型可以通过向connect()传递附加参数来指定。请注意,如果事件循环在接收方的线程中运行,那么发送方和接收方在不同的线程中使用直接连接是不安全的,原因与调用另一个线程中的对象上的任何函数是不安全的。
QObject::connect()本身是线程安全的。
Mandelbrot Example 使用QueuedConnection在工作线程和主线程之间进行通信。为了避免冻结主线线的事件循环(以及应用程序的用户界面),所有的Mandelbrot分形计算都在一个单独的工作线程中完成。当完成分形渲染时,线程会发出信号。
类似地,Blocking Fortune Client Example 使用一个单独的线程来异步地与TCP服务器通信。
Synchronizing Threads
虽然线程的目的是允许代码并行运行,但有时线程必须停止并等待其他线程。例如,如果两个线程试图同时写入同一个变量,结果是未定义的。强制线程彼此等待的原理称为互斥。这是保护共享资源(如数据)的常用技术。
Qt提供了低级原语以及高级机制来同步线程。
Low-Level Synchronization Primitives
QMutex是强制互斥的基本类。线程为了访问共享资源而锁定互斥对象。如果第二个线程在已经锁定的情况下试图锁定互斥锁,则第二个线程将被置于睡眠状态,直到第一个线程完成任务并解锁互斥锁。
QReadWriteLock类似于QMutex,不同的是它区分了“读”和“写”访问。当一段数据没有被写入时,多个线程同时读取它是安全的。QMutex强制多个读取器轮流读取共享数据,而QReadWriteLock允许同时读取数据,从而提高了并行性。
QSemaphore是QMutex的泛化,它保护一定数量的相同资源。相反,QMutex只保护一个资源。Semaphores Example展示了信号量的典型应用:在生产者和消费者之间同步对循环缓冲区的访问。
QWaitCondition不是通过强制互斥,而是通过提供一个条件变量来同步线程。其他原语使线程等待直到资源被解锁,而QWaitCondition使线程等待直到满足特定条件。要允许等待的线程继续,调用wakeOne()唤醒一个随机选择的线程,或者调用wakeAll()同时唤醒所有线程。等待条件示例展示了如何使用QWaitCondition而不是QSemaphore来解决生产者-消费者问题。
注意:Qt的同步类依赖于正确对齐指针的使用。例如,您不能在MSVC中使用打包类。
这些同步类可用于使方法线程安全。然而,这样做会导致性能损失,这就是为什么大多数Qt方法都不是线程安全的。
风险
如果一个线程锁定了一个资源但没有解锁它,应用程序可能会死锁,因为其他线程将永远无法使用该资源。例如,如果抛出异常并强制当前函数返回而不释放其锁,就会发生这种情况。
另一个类似的场景是死锁。例如,假设线程A正在等待线程B解锁一个资源。如果线程B也在等待线程A解锁一个不同的资源,那么两个线程都将永远等待,因此应用程序将死锁。
Convenience classes
QMutexLocker, QReadLocker和QWriteLocker是方便的类,使QMutex和QReadWriteLock更容易使用。它们在构造资源时锁定资源,在销毁资源时自动解锁资源。它们被设计用来简化使用QMutex和qreadwritellock的代码,从而减少资源被意外永久锁定的机会。
High-Level Event Queues
Qt的事件系统对于线程间的通信非常有用。每个线程都可以有自己的事件循环。要在另一个线程中调用槽(或任何可调用的方法),请将该调用放到目标线程的事件循环中。这让目标线程在插槽开始运行之前完成其当前任务,而原始线程继续并行运行。
要将调用置于事件循环中,请创建一个排队的信号槽连接。无论何时发出信号,它的参数都会被事件系统记录下来。信号接收器所在的线程将运行这个插槽。或者,在没有信号的情况下调用QMetaObject::invokeMethod()来达到同样的效果。在这两种情况下,都必须使用排队连接,因为直接连接会绕过事件系统,并立即在当前线程中运行该方法。
与使用低级原语不同,使用事件系统进行线程同步时不存在死锁的风险。但是,事件系统并不强制互斥。如果可调用方法访问共享数据,它们仍然必须使用低级原语进行保护。
话虽如此,Qt的事件系统,以及隐式共享数据结构,提供了传统线程锁定的替代方案。如果信号和槽是独占使用的,并且没有变量在线程之间共享,那么多线程程序完全可以不使用低级原语。
Blocking Fortune Client Example
QTcpSocket支持两种通用的网络编程方法:
- 异步(非阻塞)方法。当控制返回到Qt的事件循环时,将调度并执行操作。当操作完成时,QTcpSocket发出一个信号。例如,QTcpSocket::connectToHost()立即返回,当连接建立时,QTcpSocket发出connected()。
- 同步(阻塞)方法。在非gui和多线程应用程序中,你可以调用waitFor…()函数(例如,QTcpSocket::waitForConnected())来暂停调用线程,直到操作完成,而不是连接到信号。
这个实现与Fortune Client的例子非常相似,但是我们没有将QTcpSocket作为主类的成员,而是在主线程中执行异步网络连接,我们将在一个单独的线程中执行所有的网络操作,并使用QTcpSocket的阻塞API。
这个示例的目的是演示一个模式,您可以使用它来简化网络代码,而不会失去用户界面的响应性。使用Qt的阻塞网络API通常会导致代码更简单,但由于它的阻塞行为,应该只在非gui线程中使用它,以防止用户界面冻结。但与许多人的想法相反,使用QThread和线程并不一定会给应用程序增加难以管理的复杂性。
我们将从FortuneThread类开始,它处理网络代码。
class FortuneThread : public QThread
{
Q_OBJECT
public:
FortuneThread(QObject *parent = 0);
~FortuneThread();
void requestNewFortune(const QString &hostName, quint16 port);
void run() override;
signals:
void newFortune(const QString &fortune);
void error(int socketError, const QString &message);
private:
QString hostName;
quint16 port;
QMutex mutex;
QWaitCondition cond;
bool quit;
};
FortuneThread是一个QThread子类,它提供了一个用于调度算命请求的API,并且它有传递算法和报告错误的信号。 您可以调用requestNewFortune()来请求一个新的fortune,结果由newFortune()信号传递。 如果出现任何错误,则会发出error()信号。
需要注意的是,requestNewFortune()是从主GUI线程调用的,但它存储的主机名和端口值将从FortuneThread的线程访问。 因为我们将从不同的线程并发地读写FortuneThread的数据成员,所以我们使用QMutex来同步访问。
void FortuneThread::requestNewFortune(const QString &hostName, quint16 port)
{
QMutexLocker locker(&mutex);
this->hostName = hostName;
this->port = port;
if (!isRunning())
start();
else
cond.wakeOne();
}
requestNewFortune()函数将fortune服务器的主机名和端口存储为成员数据,我们使用QMutexLocker锁定互斥锁来保护这些数据。 然后启动线程,除非它已经在运行。 稍后我们将回到QWaitCondition::wakeOne()调用。
void FortuneThread::run()
{
mutex.lock();
QString serverName = hostName;
quint16 serverPort = port;
mutex.unlock();
在run()函数中,我们首先获取互斥锁,从成员数据中获取主机名和端口,然后再次释放锁。 我们保护自己的情况是,在获取数据的同时可以调用requestNewFortune()。 QString是可重入的,但不是线程安全的,我们还必须避免从一个请求读取主机名和另一个请求的端口的不太可能的风险。 正如您可能已经猜到的,FortuneThread一次只能处理一个请求。
run()函数现在进入一个循环:
while (!quit) {
const int Timeout = 5 * 1000;
QTcpSocket socket;
socket.connectToHost(serverName, serverPort);
只要quit为false,循环将继续请求fortunes。 我们通过在堆栈上创建QTcpSocket开始第一个请求,然后调用connectToHost()。 这将启动一个异步操作,在控制返回到Qt的事件循环后,将导致QTcpSocket发出connected()或error()。
if (!socket.waitForConnected(Timeout)) {
emit error(socket.error(), socket.errorString());
return;
}
但是由于我们运行在一个非gui线程中,我们不必担心阻塞用户界面。 因此,我们只需调用QTcpSocket::waitForConnected(),而不是进入一个事件循环。 这个函数将等待,阻塞调用线程,直到QTcpSocket发出connected()或发生错误。 如果触发了connected(),则函数返回true; 如果连接失败或超时(在本例中发生在5秒后),则返回false。 QTcpSocket::waitForConnected(),像其他的waitFor…()函数一样,是QTcpSocket的阻塞API的一部分。
在这个语句之后,我们有一个连接的套接字要处理。
QDataStream in(&socket);
in.setVersion(QDataStream::Qt_4_0);
QString fortune;
现在我们可以创建一个QDataStream对象,将套接字传递给QDataStream的构造函数,并且在其他客户机示例中,我们将流协议版本设置为QDataStream::Qt_4_0。
do {
if (!socket.waitForReadyRead(Timeout)) {
emit error(socket.error(), socket.errorString());
return;
}
in.startTransaction();
in >> fortune;
} while (!in.commitTransaction());
我们通过调用QTcpSocket::waitForReadyRead()初始化一个循环来等待fortune字符串数据。 如果返回false,则中止操作。 在这个语句之后,我们开始一个流读事务。 当QDataStream::commitTransaction()返回true时,将退出循环,这意味着成功加载了fortune字符串。 生成的fortune通过发送newFortune()来交付:
mutex.lock();
emit newFortune(fortune);
cond.wait(&mutex);
serverName = hostName;
serverPort = port;
mutex.unlock();
}
循环的最后一部分是获取互斥量,以便安全地从成员数据中读取数据。 然后通过调用QWaitCondition::wait()让线程进入睡眠状态。 在这一点上,我们可以回到requestNewFortune(),并查看对wakeOne()的调用:
void FortuneThread::requestNewFortune(const QString &hostName, quint16 port)
{
...
if (!isRunning())
start();
else
cond.wakeOne();
}
这里发生的情况是,因为线程在休眠状态下等待新请求,所以我们需要在新请求到达时再次唤醒它。 QWaitCondition经常在线程中被用来发出这样的唤醒调用信号。
FortuneThread::~FortuneThread()
{
mutex.lock();
quit = true;
cond.wakeOne();
mutex.unlock();
wait();
}
结束FortuneThread,这是一个析构函数,它将quit设置为true,唤醒线程,并在返回之前等待线程退出。 这使得run()中的while循环将完成其当前的迭代。 当run()返回时,线程将终止并被销毁。