用Qt进行多线程编程非常简单:只需要从QThread类继承,并重写run()函数即可。为了说明线程是如何工作的,我们先看一段非常简单的QThread子类,这个类在控制台上重复打印一个给定字符。程序的人机界面如下图14.1所示:
代码如下:
class Thread : public QThread
{
Q_OBJECT
public:
Thread();
void setMessage(const QString &message);
void stop();
protected:
void run();
private:
QString messageStr;
volatile bool stopped;
};
类Thread从QThread继承并重写了run()函数,并提供了两个额外的函数:setMessage() 和stop()。
变量stopped声明为volatile,是因为它需要从不同的线程中获取,我们需要保证每次读取它的时候都是重新从它的地址读取。如果不使用volatile关键字,编辑器可能有优化这个变量的存取,导致错误的结果。
Thread::Thread()
{
stopped = false;
}
在构造函数中,设置stopped变量为false。
void Thread::run()
{
while (!stopped)
std::cerr << qPrintable(messageStr);
stopped = false;
std::cerr << std::endl;
}
在开始执行线程是,函数Thread::run()就会被调用。只要变量stopped为false,函数就在控制台上打印给定的消息。当离开run()后,线程终止。
void Thread::stop()
{
stopped = true;
}
函数stop()将变量stopped置为true,通知run()停止打印向控制台打印字符。在任何时候的任何线程中,这个函数都能够调用。在本例中,我们认为给一个bool量赋值是一个原子操作。因为一个bool量只有两个值,因此这个假设是合理的。在本章的后面部分,我们将会介绍如何使用QMutex来确保一个变量赋值是一个原子操作。
QThread 提供了terminate函数来终止正在执行的线程。但是并不推荐使用terminate()函数,因为terminate()函数能够在任何时间终止线程,线程却没有时间去做可能的清理工作。我们这里使用了stopped 变量和stop(),是更加安全的做法。
我们将会看到在一个简单的Qt应用中,如何使用Thread类,在主线程之外在增加两个线程A和B。
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;
};
类 ThreadDialog 中声明了两个Thread类型的线程,并提供了一个三个按钮的用户界面。
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()));
...
}
在构造函数中,调用setMessage使线程A打印“A”,而线程B打印“B”。
当用户点击线程A按钮,如果线程A在运行,则停止线程A,否则启动线程A。同时更新按钮上显示的文本。
函数startOrStopThreadB的实现与startOrStopThreadA类似。
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();
}
如果用户点击了退出按钮或者关闭了窗口,在接受消息event->accept()之前,我们停止运行的线程并等待线程完成(调用QThread::wait()),这样就保证了程序在干净的状态下退出,当然在本例中并没有什么影响。
如果你运行程序后点击StartA按钮,控制台将会打印“A”。如果点击StartB按钮,则控制台上轮流打印“A”和“B”。点击StopA后,控制台只会打印“B”。