正点原子QT之多线程
13. 多线程
13.1 继承 QThread 的线程
继承 QThread 是创建线程的一个普通方法。 其中创建的线程只有 run()方法在线程里的。其他类内定义的方法都在主线程内。
主线程内有很多方法在主线程内, 但是子线程,只有 run()方法是在子线程里的。 run()方法是继承于 QThread 类的方法,用户需要重写这个方法,一般是把耗时的操作写在这个 run()方法里面。案例:
例 05_qthread_example1 , 继 承 QThread 类 的 线 程 ( 难 度 : 一 般 )。 项 目 路 径 为Qt/2/05_qthread_example1。 本例通过 QThread 类继承线程,然后在 MainWindow 类里使用。通过点击一个按钮开启线程。 当线程执行完成时,会发送 resultReady(const QString &s)信号给主线程。
cpp
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QThread>
#include <QDebug>
#include <QPushButton>
/* 使用下面声明的WorkerThread线程类 */
class WorkerThread;
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
/* 在MainWindow类里声明对象 */
WorkerThread *workerThread;
/* 声明一个按钮,使用此按钮点击后开启线程 */
QPushButton *pushButton;
private slots:
/* 槽函数,用于接收线程发送的信号 */
void handleResults(const QString &result);
/* 点击按钮开启线程 */
void pushButtonClicked();
};
/* 新建一个WorkerThread类继承于QThread */
class WorkerThread : public QThread
{
/* 用到信号槽即需要此宏定义 */
Q_OBJECT
public:
WorkerThread(QWidget *parent = nullptr) {
Q_UNUSED(parent);
}
/* 重写run方法,继承QThread的类,只有run方法是在新的线程里 */
void run() override {
QString result = "线程开启成功";
/* 这里写上比较耗时的操作 */
// ...
// 延时2s,把延时2s当作耗时操作
sleep(2);
/* 发送结果准备好的信号 */
emit resultReady(result);
}
signals:
/* 声明一个信号,译结果准确好的信号 */
void resultReady(const QString &s);
};
#endif // MAINWINDOW_Hcpp
#include "mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
/* 设置位置与大小 */
this->setGeometry(0, 0, 800, 480);
/* 对象实例化 */
pushButton = new QPushButton(this);
workerThread = new WorkerThread(this);
/* 按钮设置大小与文本 */
pushButton->resize(100, 40);
pushButton->setText("开启线程");
/* 信号槽连接 */
connect(workerThread, SIGNAL(resultReady(QString)),
this, SLOT(handleResults(QString)));
connect(pushButton, SIGNAL(clicked()),
this, SLOT(pushButtonClicked()));
}
MainWindow::~MainWindow()
{
/* 进程退出,注意本例run()方法没写循环,此方法需要有循环才生效 */
workerThread->quit();
/* 阻塞等待2000ms检查一次进程是否已经退出 */
if (workerThread->wait(50)) {
qDebug()<<"线程已经结束!"<<endl;
}
}
void MainWindow::handleResults(const QString &result)
{
/* 打印出线程发送过来的结果 */
qDebug()<<result<<endl;
}
void MainWindow::pushButtonClicked()
{
/* 检查线程是否在运行,如果没有则开始运行 */
if (!workerThread->isRunning())
workerThread->start();
}13.2 继承 QObject 的线程
继承 QThread 类是创建线程的一种方法,另一种就是继承QObject 类。继承 QObject 类更加灵活。 它通过 QObject::moveToThread()方法,将一个 QObeject的类转移到一个线程里执行。
首先我们写一个类继承 QObject,通过 QObject::moveToThread()方法将它移到一个 QThread 线程里执行。那么可以通过主线程发送信号去调用 QThread 线程的方法如上图的 fun4(), fun5()等等。这些方法都是在 QThread 线程里执行的。案例:
例 06_qthread_example2 , 继 承 QObject 类 的 线 程 ( 难 度 : 一 般 ) 。本例通过 QObject 类继承线程,然后在 MainWindow 类里使用。通过点击一个按钮开启线程。 另一个按钮点击关闭线程。 另外通过加锁的操作来安全的终止一个线程。(我们可以通过 QMutexLocker 可以安全的使用 QMutex 以免忘记解锁。)
cpp
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QThread>
#include <QDebug>
#include <QPushButton>
#include <QMutexLocker>
#include <QMutex>
/* 工人类 */
class Worker;
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
/* 开始线程按钮 */
QPushButton *pushButton1;
/* 打断线程按钮 */
QPushButton *pushButton2;
/* 全局线程 */
QThread workerThread;
/* 工人类 */
Worker *worker;
private slots:
/* 按钮1点击开启线程 */
void pushButton1Clicked();
/* 按钮2点击打断线程 */
void pushButton2Clicked();
/* 用于接收工人是否在工作的信号 */
void handleResults(const QString &);
signals:
/* 工人开始工作(做些耗时的操作 ) */
void startWork(const QString &);
};
/* Worker类,这个类声明了doWork1函数,将整个Worker类移至线程workerThread */
class Worker : public QObject
{
Q_OBJECT
private:
/* 互斥锁 */
QMutex lock;
/* 标志位 */
bool isCanRun;
public slots:
/* 耗时的工作都放在槽函数下,工人可以有多份不同的工作,但是每次只能去做一份 */
void doWork1(const QString ¶meter) {
/* 标志位为真 */
isCanRun = true;
/* 死循环 */
while (1) {
/* 此{}作用是QMutexLocker与lock的作用范围,获取锁后,
* 运行完成后即解锁 */
{
QMutexLocker locker(&lock);
/* 如果标志位不为真 */
if (!isCanRun) {
/* 跳出循环 */
break;
}
}
/* 使用QThread里的延时函数,当作一个普通延时 */
QThread::sleep(2);
emit resultReady(parameter + "doWork1函数");
}
/* doWork1运行完成,发送信号 */
emit resultReady("打断doWork1函数");
}
// void doWork2();...
public:
/* 打断线程(注意此方法不能放在槽函数下) */
void stopWork() {
qDebug()<<"打断线程"<<endl;
/* 获取锁后,运行完成后即解锁 */
QMutexLocker locker(&lock);
isCanRun = false;
}
signals:
/* 工人工作函数状态的信号 */
void resultReady(const QString &result);
};
#endif // MAINWINDOW_Hcpp
#include "mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
/* 设置显示位置与大小 */
this->setGeometry(0, 0, 800, 480);
pushButton1 = new QPushButton(this);
pushButton2 = new QPushButton(this);
/* 设置按钮的位置大小 */
pushButton1->setGeometry(300, 200, 80, 40);
pushButton2->setGeometry(400, 200, 80, 40);
/* 设置两个按钮的文本 */
pushButton1->setText("开启线程");
pushButton2->setText("打断线程");
/* 工人类实例化 */
worker = new Worker;
/* 将worker类移至线程workerThread */
worker->moveToThread(&workerThread);
/* 信号槽连接 */
/* 线程完成销毁对象 */
connect(&workerThread, SIGNAL(finished()),
worker, SLOT(deleteLater()));
connect(&workerThread, SIGNAL(finished()),
&workerThread, SLOT(deleteLater()));
/* 发送开始工作的信号,开始工作 */
connect(this, SIGNAL(startWork(QString)),
worker, SLOT(doWork1(QString)));
/* 接收到worker发送过来的信号 */
connect(worker, SIGNAL(resultReady(QString)),
this, SLOT(handleResults(QString)));
/* 点击按钮开始线程 */
connect(pushButton1, SIGNAL(clicked()),
this, SLOT(pushButton1Clicked()));
/* 点击按钮打断线程 */
connect(pushButton2, SIGNAL(clicked()),
this, SLOT(pushButton2Clicked()));
}
MainWindow::~MainWindow()
{
/* 打断线程再退出 */
worker->stopWork();
workerThread.quit();
/* 阻塞线程2000ms,判断线程是否结束 */
if (workerThread.wait(2000)) {
qDebug()<<"线程结束"<<endl;
}
}
void MainWindow::pushButton1Clicked()
{
/* 字符串常量 */
const QString str = "正在运行";
/* 判断线程是否在运行 */
if(!workerThread.isRunning()) {
/* 开启线程 */
workerThread.start();
}
/* 发送正在运行的信号,线程收到信号后执行后返回线程耗时函数 + 此字符串 */
emit this->startWork(str);
}
void MainWindow::pushButton2Clicked()
{
/* 如果线程在运行 */
if(workerThread.isRunning()) {
/* 停止耗时工作,跳出耗时工作的循环 */
worker->stopWork();
}
}
void MainWindow::handleResults(const QString & results)
{
/* 打印线程的状态 */
qDebug()<<"线程的状态:"<<results<<endl;
}
阿源的知识库