前言:QThread 线程类是实现多线程的核心类。Qt 有两种多线程的方法,其中一种是继承 QThread的 run()函数,另外一种是把一个继承于 QObject 的类转移到一个 Thread 里。Qt4.8 之前都是使用继承 QThread 的 run()这种方法,但是 Qt4.8 之后,Qt 官方建议使用第二种方法。两种方法区别不大,用起来都比较方便,但继承 QObject 的方法更加灵活。所以 Qt 的帮助文档里给的参考是先给继承 QObject 的类,然后再给继承 QThread 的类。
mainwindow.h
当线程执行完成时,会发送 resultReady(const QString &s)信号给主线程。重写 run()方法,这里很重要。把耗时操作写于此,本例相当于一个继承 QThread类线程模板了。
#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 = "线程开启成功";
/* 发送结果准备好的信号 */
emit resultReady(result);
while (1) {
emit resultReady("正在运行中......");
sleep(1);
}
}
signals:
/* 声明一个信号,译结果准确好的信号 */
void resultReady(const QString &s);
};
#endif // MAINWINDOW_H
mainwindow.cpp
在 MainWindow 的析构函数里退出线程,然后判断线程是否退出成功。因为我们这个线程是没有循环操作的,直接点击按钮开启线程后,开始做耗时工作。按钮点击后开启线程,首先我们得判断这个线程是否在运行,如果不在运行我们则开始线程,开始线程用 start()方法,它会调用重写的 run()函数的。
#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();
}
}
main.cpp
默认就是工程创建好的样子没有改动
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
mainwindow.h
说明:我们把耗时的工作都放于槽函数下。工人可以有不同的工作,但是每次只能去做一份。这里不同于继承 QThread 类的线程 run(),继承 QThread 的类只有 run()在新线程里。而继承 QObject 的类,使用 moveToThread()可以把整个继承的 QObject 类移至线程里执行,所以可以有 doWork1(),doWork2…等等耗时的操作,但是这些耗时的操作都应该作为槽函数,由主线程去调用。进入循环后使用互拆锁判断 isCanRun 变量的状态,为假即跳出 while 循环,直到 doWork1 结束。注意,虽然 doWork1 结束了,但是线程并没有退出(结束)。因为我们把这个类移到线程里了,直到这个类被销毁。或者使用 quit()和 exit()退出线程才真正的结束!
#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:
/* 开始工作1线程按钮 */
QPushButton *pushButton1;
/* 打断线程按钮 */
QPushButton *pushButton2;
/* 开始工作2线程按钮 */
QPushButton *pushButton3;
/* 全局线程 */
QThread workerThread;
/* 工人类 */
Worker *worker;
private slots:
/* 按钮1点击工作1线程 */
void pushButton1Clicked();
/* 按钮2点击打断线程 */
void pushButton2Clicked();
/* 按钮3点击工作2线程 */
void pushButton3Clicked();
/* 用于接收工人是否在工作的信号 */
void handleResults(const QString &);
signals:
/* 工人开始工作(做些耗时的操作 ) */
void startWork1(const QString &);
void startWork2(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(1);
emit resultReady(parameter + "doWork1函数" + "运行中......");
}
/* doWork1运行完成,发送信号 */
emit resultReady("打断doWork1函数");
}
void doWork2(const QString ¶meter)
{
/* 标志位为真 */
isCanRun = true;
/* 死循环 */
while (1) {
/* 此{}作用是QMutexLocker与lock的作用范围,获取锁后,
* 运行完成后即解锁 */
{
QMutexLocker locker(&lock);
/* 如果标志位不为真 */
if (!isCanRun) {
/* 跳出循环 */
break;
}
}
/* 使用QThread里的延时函数,当作一个普通延时 */
QThread::sleep(1);
emit resultReady(parameter + "doWork2函数" + "运行中......");
}
/* doWork1运行完成,发送信号 */
emit resultReady("打断doWork2函数");
}
public:
/* 打断线程(注意此方法不能放在槽函数下) */
void stopWork()
{
qDebug() << "打断线程" << endl;
/* 获取锁后,运行完成后即解锁 */
QMutexLocker locker(&lock);
isCanRun = false;
}
signals:
/* 工人工作函数状态的信号 */
void resultReady(const QString &result);
};
#endif // MAINWINDOW_H
mainwindow.cpp
快速了解继承 QObject 类线程的使用。
在 MainWindow 类里使用。通过点击一个按钮开启线程。另一个按钮点击关闭线程。另外通过加锁的操作来安全的终止一个
线程。(我们可以通过 QMutexLocker 可以安全的使用 QMutex 以免忘记解锁。)在我们谈谈为什么需要加锁来终止一个线程?因为 quit()和 exit()方法都不会中途终止线程。要马上终止一个线程可以用 terminate()方法。但是这个函数存在非常不安全的因素,Qt 官方文档说不推荐使用。我们可以添加一个 bool 变量,通过主线程修改这个 bool 变量来终止,但是有可能引起访问冲突,所以需要加锁,例程里可能体现不是那么明确,当我们有 doWork1(),doWork2…就能体现到 bool 变量加锁的作用了。但是加锁会消耗一定的性能,增加耗时。
#include "mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
/* 设置显示位置与大小 */
this->setGeometry(0, 0, 800, 480);
pushButton1 = new QPushButton(this);
pushButton2 = new QPushButton(this);
pushButton3 = new QPushButton(this);
/* 设置按钮的位置大小 */
pushButton1->setGeometry(100, 200, 120, 40);
pushButton2->setGeometry(300, 200, 120, 40);
pushButton3->setGeometry(500, 200, 120, 40);
/* 设置两个按钮的文本 */
pushButton1->setText("开启工作1线程");
pushButton2->setText("打断线程");
pushButton3->setText("开启工作2线程");
/* 工人类实例化 */
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(startWork1(QString)),
worker, SLOT(doWork1(QString)));
/* 发送开始工作的信号,开始工作 */
connect(this, SIGNAL(startWork2(QString)),
worker, SLOT(doWork2(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()));
connect(pushButton3, SIGNAL(clicked()),
this, SLOT(pushButton3Clicked()));
}
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->startWork1(str);
}
void MainWindow::pushButton3Clicked()
{
/* 字符串常量 */
const QString str = "正在运行";
/* 判断线程是否在运行 */
if (!workerThread.isRunning()) {
/* 开启线程 */
workerThread.start();
}
/* 发送正在运行的信号,线程收到信号后执行后返回线程耗时函数 + 此字符串 */
emit this->startWork2(str);
}
void MainWindow::pushButton2Clicked()
{
/* 如果线程在运行 */
if (workerThread.isRunning()) {
/* 停止耗时工作,跳出耗时工作的循环 */
worker->stopWork();
}
}
void MainWindow::handleResults(const QString &results)
{
/* 打印线程的状态 */
qDebug() << "线程的状态:" << results << endl;
}
main.cpp
默认就是工程创建好的样子没有改动
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
下面是演示效果
C++线程使用