通常情况下,应用程序都是在一个线程中执行操作。但是,当调用一个耗时操作(例如,大批量I/O或大量矩阵变换等CPU密集操作)时,用户界面常常会冻结。而使用多线程可解决这一问题。
多线程具有以下几点优势:
(1)提高应用程序的响应速度。这对于开发图形界面的程序尤为重要,当一个操作耗时很长时,整个系统都会等待这个操作,程序就不能响应键盘、鼠标、菜单等的操作,而使用多线程技术可将耗时长的操作置于一个新的线程,从而避免以上的问题。
(2)使多CPU系统更加有效。当线程数不大于CPU数目时,操作系统可以调度不同的线程运行于不同的CPU上。
(3)改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为独立或半独立的运行部分,这样有利于代码的理解和维护。
多线程程序有以下几个特点:
(1)多线程程序的行为无法预期,当多次执行上述程序时,每一次的运行结果都可能不同。
(2)多线程的执行顺序无法保证,它与操作系统的调度策略和线程优先级等因素有关。
(3)多线程的切换可能发生在任何时刻、任何地点。
(4)多线程对代码的敏感度高,因此对代码的细微修改都可能产生意想不到的结果。
基于以上这些特点,为了有效地使用线程,开发人员必须对其进行控制。
下面的例子介绍如何实现一个简单的多线程程序。
【例】(难度一般)(CH1201)如图12.1所示,单击“开始”按钮将启动数个工作线程(工作线程数目由MAXSIZE宏决定),各个线程循环打印数字0~9,直到单击“停止”按钮终止所有线程为止。
具体步骤如下:
(1)在头文件“threaddlg.h”中声明用于界面所需的控件,其具体代码如下:
#include
#include
class ThreadDlg : public QDialog
{
Q_OBJECT
public:
ThreadDlg(QWidget *parent = 0);
~ThreadDlg();
private:
QPushButton *startBtn;
QPushButton *stopBtn;
QPushButton *quitBtn;
};
(2)在源文件“threaddlg.cpp”的构造函数中,完成各个控件的初始化工作,其具体代码如下:
#include "threaddlg.h"
#include
#pragma execution_character_set("utf-8")
ThreadDlg::ThreadDlg(QWidget *parent)
: QDialog(parent)
{
setWindowTitle(tr("线程"));
startBtn = new QPushButton(tr("开始"));
stopBtn = new QPushButton(tr("停止"));
quitBtn = new QPushButton(tr("退出"));
QHBoxLayout *mainLayout = new QHBoxLayout(this);
mainLayout->addWidget(startBtn);
mainLayout->addWidget(stopBtn);
mainLayout->addWidget(quitBtn);
}
(3)此时运行程序,界面显示如图12.1所示。
以上完成了界面的设计,下面的内容是具体的功能实现。
(1)在头文件“workthread.h”中,工作线程WorkThread类继承自QThread类。重新实现run()函数。其具体代码如下:
#include
class WorkThread : public QThread
{
Q_OBJECT
public:
WorkThread();
protected:
void run();
};
(2)在源文件“workthread.cpp”中添加具体实现代码如下:
#include "workthread.h"
#include
WorkThread::WorkThread()
{
}
run()函数实际上是一个死循环,它不停地打印数组0~9。为了显示效果明显,程序将每一个数字重复打印8次。
void WorkThread::run()
{
while (true)
{
for (int n = 0; n <10; n++)
qDebug()<
注意:线程将因为用printf()而持有一个控制I/O的锁(lock),多个线程同时调用printf()函数在某些情况下将造成控制台输出阻塞,而使用Qt提供的qDebug()函数作为控制台输出则不会出现上述问题。
(3)在头文件“threaddlg.h”中添加一下内容:
#include "workthread.h"
#define MAXSIZE 1 //MAXSIZE宏定义了线程的数目
public slots:
void slotStart(); //槽函数用于启动线程
void slotStop(); //槽函数用于终止线程
private:
WorkThread *workThread[MAXSIZE]; //(a)
其中,(a) WorkThread *workThread[MAXSIZE]: 指向工作线程(WorkThread)的私有指针数组workThread,记录了所启动的全部线程。
(4)在源文件“threaddlg.cpp”中添加一下内容。
其中,在构造函数中添加如下代码:
connect(startBtn, SIGNAL(clicked()), this, SLOT(slotStart()));
connect(stopBtn, SIGNAL(clicked()), this, SLOT(slotStop()));
connect(quitBtn, SIGNAL(clicked()), this, SLOT(close()));
槽函数slotStart(),当用户单击“开始”按钮时,次函数将被调用。这里使用两个循环,目的是为了使新建的线程尽可能同时开始执行,其具体实现代码如下:
void ThreadDlg::slotStart()
{
for (int i = 0; i < MAXSIZE; i++)
{
workThread[i] = new WorkThread(); //(a)
}
for (int i = 0; i < MAXSIZE; i++)
{
workThread[i]->start(); //(b)
}
startBtn->setEnabled(false);
stopBtn->setEnabled(true);
}
其中,(a) workThread[i] = new WorkThread():创建指定数目的WorkThread线程,并将WorkThread实例的指针保存到指针数组workThread中。
(b) workThread[i]->start():调用QThread基类的start()函数,此函数将启动函数run(),从而使得线程开始真正运行。
槽函数slotStop(),当用户单击“停止”按钮时,此函数将被调用。其具体实现代码如下:
void ThreadDlg::slotStop()
{
for (int i = 0; i < MAXSIZE; i++)
{
workThread[i]->terminate();
workThread[i]->wait();
}
startBtn->setEnabled(true);
stopBtn->setEnabled(false);
}
其中,workThread[i]->terminate()、workThread[i]->wait():调用QThread基类的terminate()函数,依次终止保存在workThread[]数组中的WorkThread类实例。但是,terminate()函数并不会立刻终止这个线程,该线程何时终止取决于操作系统的调度策略。因此,程序紧接着调用了QThread基类的wait()函数,它使得线程阻塞等待直到退出或超时。
(5)运行结果如图12.2所示。
第1列是启动5个线程的运行结果,第2列是启动单一线程的运行结果。可以看出,单一线程的输出是顺序打印的,而多线程的输出结果则是乱序打印的,这正是多线程的一大特点。
完整代码下载地址:https://download.csdn.net/download/anhuizhj/10958574
参考资料:Qt 5 开发及实例(第3版)