创建线程
在Qt应用程序中提供多钱程是非常简单的:只需要子类化QThread 并且重新实现它的 run()函数就可以了。为了显示这一过程是如何进行的,我们将从介绍一个非常简单的QThread子类的代码开始,它是一个可以在控制台上重复打印给定字符串的子类。应用程序的用户接口界面如:
Thread.h
#ifndef THREAD_H
#define THREAD_H
#include <QThread>
class Thread : public QThread
{
Q_OBJECT
public:
Thread();
void setMessage(const QString &message);
void stop();
protected:
void run();
private:
QString messageStr;
volatile bool stopped;
};
#endif
这里的Thread 类派生自QThread 类,并且重新实现了 run() 函数。这个类还额外提供了两个函数:因setMessage()和 stop()。
stopped 被声明为易失性变量(volatile variable,断电或中断时数据丢失而不可再恢复的变量型) ,这是因为不同的线程都需要访问它,并且我们也希望确保它能够在任何需要的时候都保最新读取的数值。如果省略 volatile 关键字,则编译器就会对这个变量的访问进行优化,那么就能导致不正确的结果。
Thread.cpp
#include <QtCore>
#include <iostream>
#include "thread.h"
Thread::Thread():stopped(false){} //构造函数
void Thread::setMessage(const QString &message)
{
messageStr = message;
}
void Thread::run()
{
while (!stopped)
std::cerr << qPrintable(messageStr);
stopped = false;
std::cerr << std::endl;
}
void Thread::stop()
{
stopped = true;
}
Thread()
在构造图数中把 stopped 设置为 false。
run()
在开始执行线程的时,就会调用 run() 函数。只要 stopped 变量为 false 值,这个函数就会一向控制台打印输出给定的消息。当控制离开 run() 函数时,就会终止线程。
stop()
stop()函数会把 stopped 变量设置为 true ,从而告诉run()停止向控制台输出文本。任何时候stop()函数都可以由任一线程调用。考虑到这个实例的目的,我们假定对bool变量的赋值是一个原子操作。这是一个合理的假设,因为bool变量仅能有两种状态。在这一节的稍后部分,到如何使用 QMutex 确保对一个变量的赋值成为一个原子操作。
QThread提供了一个terminate()函数,该函数可以在一个线程还在运行的时候就终止它的执行。我们不推荐使用terminate(),这是因为它可以随时停止线程执行而不给这个线程自我清空的机会。一种更为安全的方法是使用stopped变量和stop()函数,就像这里所做的那样。
ThreadDialog.h
下面将给出如何在一个简单的Qt应用程序中使用Thread类的示例,这个应用程序除了主线程之外还使用了两个线程,即线程 A和线程 B。
#ifndef THREADDIALOG_H
#define THREADDIALOG_H
#include <QDialog>
#include "thread.h"
class QPushButton;
class ThreadDialog : public QDialog
{
Q_OBJECT
public:
ThreadDialog(QWidget *parent = 0);
protected:
void closeEvent(QCloseEvent *event);
private slots:
void startOrStopThreadA();
void startOrStopThreadB();
private:
Thread threadA;
Thread threadB;
QPushButton *threadAButton;
QPushButton *threadBButton;
QPushButton *quitButton;
};
#endif
ThreadDialog 类声明了两个类型为Thread 的变量和一些按钮,提供基本的用户界面。
ThreadDialog.cpp
#include <QtGui>
#include "threaddialog.h"
ThreadDialog::ThreadDialog(QWidget *parent)
: QDialog(parent)
{
threadA.setMessage("A");
threadB.setMessage("B");
threadAButton = new QPushButton(tr("Start A"));
threadBButton = new QPushButton(tr("Start B"));
quitButton = new QPushButton(tr("Quit"));
quitButton->setDefault(true);
connect(threadAButton, SIGNAL(clicked()),
this, SLOT(startOrStopThreadA()));
connect(threadBButton, SIGNAL(clicked()),
this, SLOT(startOrStopThreadB()));
connect(quitButton, SIGNAL(clicked()), this, SLOT(close()));
QHBoxLayout *mainLayout = new QHBoxLayout;
mainLayout->addWidget(threadAButton);
mainLayout->addWidget(threadBButton);
mainLayout->addWidget(quitButton);
setLayout(mainLayout);
setWindowTitle(tr("Threads"));
}
void ThreadDialog::startOrStopThreadA()
{
if (threadA.isRunning()) {
threadA.stop();
threadAButton->setText(tr("Start A"));
} else {
threadA.start();
threadAButton->setText(tr("Stop A"));
}
}
void ThreadDialog::startOrStopThreadB()
{
if (threadB.isRunning()) {
threadB.stop();
threadBButton->setText(tr("Start B"));
} else {
threadB.start();
threadBButton->setText(tr("Stop B"));
}
}
void ThreadDialog::closeEvent(QCloseEvent *event)
{
threadA.stop();
threadB.stop();
threadA.wait();
threadB.wait();
event->accept();
}
ThreadDialog()
在构造函数中,调用 setMessage()让第一个线程重复打印字母"A" ,而让第二个线程重复打印宇母"B" 。
startOrStopThreadA()
当用户单击用于线程的按钮时,如果这个线程正在运行, startOrStopThreadA()就让它停止运行;否则,就让它开始运行。startOrStopThreadA()同时还更新该按钮上的文本。
startOrStopThreadB()
startOrStopThreadB()的代码与startOrStopThreadA()的代码在结构上基本一致。
closeEvent()
如果用户单击了Quit或者关闭了窗口,就停止所有正在运行的线程,并且在调用函数QCloseEvent::accept()之前等待它们完全结束[可使用QThread::wait()]。这样就可以确保应用程序是以一种原始清空的状态而退出的,尽管在这个实例中是否这样做并不要紧。
如果运行这个应用程序,并且单击按钮"Start A",那么控制台终端就会被连续输出的字母"A"填满。如果再单击按钮"Start B",那么控制台终端就会被以交替顺序输出的字母"A"和"B"填满。如果再单击按钮"Stop A",那么就将只输出字母"B"。
main.cpp
#include <QApplication>
#include "threaddialog.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
ThreadDialog dialog;
dialog.show();
return app.exec();
}