12.1 多线程及简单实例

第12章 Qt 5 多线程

通常情况下,应用程序都是在一个线程中执行操作。但是,当调用一个耗时操作(例如,大批量I/O或大量矩阵变换等CPU密集操作)时,用户界面常常会冻结。而使用多线程可解决这一问题。

多线程具有以下几点优势:

(1)提高应用程序的响应速度。这对于开发图形界面的程序尤为重要,当一个操作耗时很长时,整个系统都会等待这个操作,程序就不能响应键盘、鼠标、菜单等的操作,而使用多线程技术可将耗时长的操作置于一个新的线程,从而避免以上的问题。

(2)使多CPU系统更加有效。当线程数不大于CPU数目时,操作系统可以调度不同的线程运行于不同的CPU上。

(3)改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为独立或半独立的运行部分,这样有利于代码的理解和维护。

多线程程序有以下几个特点:

(1)多线程程序的行为无法预期,当多次执行上述程序时,每一次的运行结果都可能不同。

(2)多线程的执行顺序无法保证,它与操作系统的调度策略和线程优先级等因素有关。

(3)多线程的切换可能发生在任何时刻、任何地点。

(4)多线程对代码的敏感度高,因此对代码的细微修改都可能产生意想不到的结果。

基于以上这些特点,为了有效地使用线程,开发人员必须对其进行控制。

12.1 多线程及简单实例

下面的例子介绍如何实现一个简单的多线程程序。

【例】(难度一般)(CH1201)如图12.1所示,单击“开始”按钮将启动数个工作线程(工作线程数目由MAXSIZE宏决定),各个线程循环打印数字0~9,直到单击“停止”按钮终止所有线程为止。

具体步骤如下:

(1)在头文件“threaddlg.h”中声明用于界面所需的控件,其具体代码如下:

#include <QDialog>
#include <QPushButton>
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 <QHBoxLayout>
#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所示。

图12.1 多线程简单实现界面​​​

以上完成了界面的设计,下面的内容是具体的功能实现。

(1)在头文件“workthread.h”中,工作线程WorkThread类继承自QThread类。重新实现run()函数。其具体代码如下:

#include <QThread>
class WorkThread : public QThread
{
    Q_OBJECT
public:
    WorkThread();
protected:
    void run();
};

(2)在源文件“workthread.cpp”中添加具体实现代码如下:

#include "workthread.h"
#include <QtDebug>
WorkThread::WorkThread()
{
}

run()函数实际上是一个死循环,它不停地打印数组0~9。为了显示效果明显,程序将每一个数字重复打印8次。

void WorkThread::run()
{
    while (true)
    {
        for (int n = 0; n <10; n++)
            qDebug()<<n<<n<<n<<n<<n<<n<<n<<n;
    }
}

注意:线程将因为用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列是启动单一线程的运行结果。可以看出,单一线程的输出是顺序打印的,而多线程的输出结果则是乱序打印的,这正是多线程的一大特点。

图12.2 多线程简单实现结果

完整代码下载地址:https://download.csdn.net/download/anhuizhj/10958574

参考资料:Qt 5 开发及实例(第3版) 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值