大家好,今天主要和大脚聊一聊,如何使用QT中的多线程的方法。
第一:多线程基本简介
QThread 线程类是实现多线程的核心类。Qt 有两种多线程的方法,其中一种是继承 QThread 的 run()函数,另外一种是把一个继承于 QObject 的类转移到一个 Thread 里。Qt4.8 之前都是使用继承 QThread 的 run()这种方法,但是 Qt4.8 之后,Qt 官方建议使用第二种方法。两种方法区 别不大,用起来都比较方便,但继承 QObject 的方法更加灵活。所以 Qt 的帮助文档里给的参考是先给继承 QObject 的类,然后再给继承 QThread 的类。另外 Qt 提供了 QMutex、QMutexLocker、QReadLocker 和 QWriteLocker 等类用于线程之间的同步,详细可以看 Qt 的帮助文档。
通过上面的图我们可以看到,主线程内有很多方法在主线程内,但是子线程,只有
run() 方法是在子线程里的。run()
方法是继承于
QThread
类的方法,用户需要重写这个方法,一般是把耗时的操作写在这个 run()
方法里面。
第二:应用实例实现
通过 QThread 类继承线程,然后在 MainWindow 类里使用。通过点击一个按钮开启线程。当线程执行完成时,会发送 resultReady(const QString &s)信号给主线程。
1 #ifndef MAINWINDOW_H
2 #define MAINWINDOW_H
3
4 #include <QMainWindow>
5 #include <QThread>
6 #include <QDebug>
7 #include <QPushButton>
8
9 /* 使用下面声明的 WorkerThread 线程类 */
10 class WorkerThread;
11
12 class MainWindow : public QMainWindow
13 {
14 Q_OBJECT
15
16 public:
17 MainWindow(QWidget *parent = nullptr);
18 ~MainWindow();
19
20 private:
21 /* 在 MainWindow 类里声明对象 */
22 WorkerThread *workerThread;
23
24 /* 声明一个按钮,使用此按钮点击后开启线程 */
25 QPushButton *pushButton;
26
27 private slots:
28 /* 槽函数,用于接收线程发送的信号 */
29 void handleResults(const QString &result);
30
31 /* 点击按钮开启线程 */
32 void pushButtonClicked();
33 };
34
35 /* 新建一个 WorkerThread 类继承于 QThread */
36 class WorkerThread : public QThread
37 {
38 /* 用到信号槽即需要此宏定义 */
39 Q_OBJECT
40
41 public:
42 WorkerThread(QWidget *parent = nullptr) {
43 Q_UNUSED(parent);
44 }
45
46 /* 重写 run 方法,继承 QThread 的类,只有 run 方法是在新的线程里 */
47 void run() override {
48 QString result = "线程开启成功";
49
50 /* 这里写上比较耗时的操作 */
51 // ...
52 // 延时 2s,把延时 2s 当作耗时操作
53 sleep(2);
54
55 /* 发送结果准备好的信号 */
56 emit resultReady(result);
57 }
58
59 signals:
60 /* 声明一个信号,译结果准确好的信号 */
61 void resultReady(const QString &s);
62 };
63
64 #endif // MAINWINDOW_H
65
第
36
行,声明一个
WorkerThread
的类继承
QThread
类,这里是参考
Qt
的
QThread
类的帮
助文档的写法。
第
47
行,重写
run()
方法,这里很重要。把耗时操作写于此,本例相当于一个继承
QThread
类线程模板了。
第三:源文件的代码具体实现
1 #include "mainwindow.h"
2
3 MainWindow::MainWindow(QWidget *parent)
4 : QMainWindow(parent)
5 {
6 /* 设置位置与大小 */
7 this->setGeometry(0, 0, 800, 480);
8
9 /* 对象实例化 */
10 pushButton = new QPushButton(this);
11 workerThread = new WorkerThread(this);
12
13 /* 按钮设置大小与文本 */
14 pushButton->resize(100, 40);
15 pushButton->setText("开启线程");
16
17 /* 信号槽连接 */
18 connect(workerThread, SIGNAL(resultReady(QString)),
19 this, SLOT(handleResults(QString)));
20 connect(pushButton, SIGNAL(clicked()),
21 this, SLOT(pushButtonClicked()));
22 }
23
24 MainWindow::~MainWindow()
25 {
26 /* 进程退出,注意本例 run()方法没写循环,此方法需要有循环才生效 */
27 workerThread->quit();
28
29 /* 阻塞等待 2000ms 检查一次进程是否已经退出 */
30 if (workerThread->wait(2000)) {
31 qDebug()<<"线程已经结束!"<<endl;
32 }
33 }
34
35 void MainWindow::handleResults(const QString &result)
36 {
37 /* 打印出线程发送过来的结果 */
38 qDebug()<<result<<endl;
39 }
40
41 void MainWindow::pushButtonClicked()
42 {
43 /* 检查线程是否在运行,如果没有则开始运行 */
44 if (!workerThread->isRunning())
45 workerThread->start();
46 }
第
11
行,线程对象实例化,
Qt
使用
C++
基本都是对象编程,
Qt
线程也不例外。所以我们
也是用对象来管理线程的。
第
24~33
行,在
MainWindow
的析构函数里退出线程,然后判断线程是否退出成功。因为
我们这个线程是没有循环操作的,直接点击按钮开启线程后,做了
2s
延时操作后就完成了。所
以我们在析构函数里直接退出没有关系。
第
41~46
行,按钮点击后开启线程,首先我们得判断这个线程是否在运行,如果不在运行
我们则开始线程,开始线程用
start()
方法,它会调用重写的
run()
函数的。
第四:程序运行效果
点击开启线程按钮后,延时 2s 后,Qt Creator 的应用程序输出窗口打印出“线程开启成功”。
在
2s
内多次点击按钮则不会重复开启线程,因为线程在这
2s
内还在运行。同时我们可以看到
点击按钮没卡顿现象。因为这个延时操作是在我们创建的线程里运行的,而
pushButton
是在主
线程里的,通过点击按钮控制子线程的运行。