大家好,今天主要和大脚聊一聊,如何使用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 是在主
线程里的,通过点击按钮控制子线程的运行。