第十四章 多线程
QThread 是 Qt 中实现多线程编程的核心类,提供跨平台线程管理。
使用 QThread 有两种方法:
1、 继承 QThread:重写 run() 方法,实现线程的具体操作。Qt4.8 之前较常用。
2、 使用 QObject 和 moveToThread():创建一个继承 QObject 的工作对象,使用 moveToThread() 将其移动到一个 QThread 对象中运行。官方推荐。
//当你调用moveToThread()时,
//你指定的QObject及其所有子对象将会在与该QThread对象相关联的线程中执行它们的槽函数。
void QObject::moveToThread(QThread *thread);
14.1 继承 QThread 的线程
继承QThread是创建线程的一种方法。
在这种方法中,只有重写的run()方法运行在子线程中,其他在类内定义的方法仍然在主线程中执行。用户需重写run()方法,并将耗时操作置于其中。
用 connect关联 QThread类的对象的信号和 QWidget的槽函数。
14.1.1 应用实例
// 告诉线程启动
QThread::start();
// 告诉线程退出
QThread::quit();
// 阻塞等待线程真正结束
QThread::wait();
本例展示了如何通过继承QThread类来创建和使用线程。在MainWindow类中,我们使用了一个按钮来启动线程,并在线程完成后接收其发送的信号。
#ifndef MAINWINDOW_H // 防止头文件重复包含
#define MAINWINDOW_H
#include <QMainWindow> // 引入QMainWindow类
#include <QThread> // 引入QThread类
#include <QPushButton> // 引入QPushButton类
// 前向声明WorkerThread类
class WorkerThread;
class MainWindow : public QMainWindow {
Q_OBJECT // 使用Qt的宏,支持信号槽机制
public:
MainWindow(QWidget *parent = nullptr); // 构造函数
~MainWindow(); // 析构函数
private:
WorkerThread *workerThread; // 工作线程对象指针
QPushButton *pushButton; // 按钮对象指针
private slots:
void handleResults(const QString &result); // 处理线程结果的槽函数
void pushButtonClicked(); // 按钮点击的槽函数
};
// WorkerThread类继承自QThread
class WorkerThread : public QThread {
Q_OBJECT // 使用Qt的宏,支持信号槽机制
public:
WorkerThread(QWidget *parent = nullptr) { Q_UNUSED(parent); } // 构造函数,忽略parent参数
// 重写run方法,线程在此方法中执行耗时操作
void run() override {
QString result = "线程开启成功"; // 定义结果字符串
sleep(2); // 模拟耗时操作,延时2秒
emit resultReady(result); // 发射结果准备好的信号
/*使用emit关键字发射一个信号时,该信号会被发送给所有与这个信号相连接的槽(slots)*/
}
signals:
// 声明一个信号,当线程完成工作时发射
void resultReady(const QString &s);
};
#endif // MAINWINDOW_H // 头文件结束标志
#include "mainwindow.h" // 引入MainWindow头文件
// MainWindow构造函数,初始化UI和线程
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) {
// 创建并设置按钮
pushButton = new QPushButton("开启线程", this);
// 创建工作线程对象
workerThread = new WorkerThread(this);
// 连接工作线程的信号到MainWindow的槽函数
connect(workerThread, SIGNAL(resultReady(QString)), this, SLOT(handleResults(QString)));
// 连接按钮点击信号到MainWindow的槽函数
connect(pushButton, SIGNAL(clicked()), this, SLOT(pushButtonClicked()));
}
// MainWindow析构函数,处理线程关闭
MainWindow::~MainWindow() {
// 告诉线程退出
workerThread->quit();
// 阻塞等待线程真正结束
workerThread->wait();
}
// 处理线程结果的槽函数
void MainWindow::handleResults(const QString &result) {
// 使用qDebug输出线程结果
qDebug() << result;
}
// 按钮点击的槽函数
void MainWindow::pushButtonClicked() {
// 检查线程是否已在运行,如果没有则启动线程
if (!workerThread->isRunning()) {
workerThread->start();
}
}
按钮点击后,QThread类的start()函数开始运行,run()函数执行,
2s后发送 emit resultReady(result);信号,触发槽函数打印结果。
14.2 继承 QObject 的线程
继承QObject类并使用 QObject::moveToThread()方法。
继承QObject类的方法更为灵活,因为它允许将一个QObject对象转移到另一个线程中执行。
14.2.1 应用实例
mainWindow窗口类有 Worker:QObject成员,QThread成员
Worker中定义任务,使用 QObject::moveToThread(&qthread)来代替重写QThread的run方法,使得线程可以执行这个任务。
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QThread>
#include <QDebug>
#include <QPushButton>
#include <QMutexLocker>
#include <QMutex>
// 前向声明Worker类
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:
void pushButton1Clicked(); // 开启线程槽函数
void pushButton2Clicked(); // 打断线程槽函数
void handleResults(const QString &); // 处理工作结果槽函数
signals:
void startWork(const QString &); // 发送开始工作信号
};
// Worker类,用于处理耗时任务
class Worker : public QObject {
Q_OBJECT
private:
QMutex lock; // 互斥锁,用于线程同步
bool isCanRun; // 控制工作是否可以继续的标志位
public slots:
void doWork1(const QString ¶meter) {
isCanRun = true;
// 使用互斥锁保护共享资源,并在条件不满足时退出循环
while (true) {
QMutexLocker locker(&lock);
if (!isCanRun) break;
}
QThread::sleep(2); // 模拟耗时操作,秒
// 发射工作结果信号
emit resultReady(parameter + " doWork1 函数");
emit resultReady("打断 doWork1 函数"); // 通知工作已完成(此处设计可能不合理,通常只需在工作结束时发射一次信号)
}
public:
void stopWork() {
qDebug() << "打断线程";
/*创建了一个 QMutexLocker 对象,
并立即尝试锁定传递给它的 QMutex 对象(即 lock)。
如果 lock 已经被另一个线程锁定,那么这行代码会阻塞当前线程,直到 lock 被释放为止。*/
QMutexLocker locker(&lock);
isCanRun = false; // 设置标志位,停止工作
}
signals:
void resultReady(const QString &result); // 工作结果信号
};
#endif // MAINWINDOW_H
#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);
// 实例化Worker对象,并将其移动到workerThread线程中
worker = new Worker;
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)));
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();
workerThread.wait(2000); // 阻塞等待2秒
qDebug() << "线程结束" << endl;
}
void MainWindow::pushButton1Clicked() {
if (!workerThread.isRunning()) {
workerThread.start();
}
emit startWork("正在运行");
}
void MainWindow::pushButton2Clicked() {
if (workerThread.isRunning()) {
worker->stopWork();
}
}
void MainWindow::handleResults(const QString &results) {
qDebug() << "线程的状态:" << results << endl;
}