第十四章 多线程
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;
}