qt-C++笔记之QThread使用
——2024-05-26 下午
code review!
参考博文:
qt-C++笔记之使用QtConcurrent异步地执行槽函数中的内容,使其不阻塞主界面
qt-C++笔记之QThread使用
文章目录
- qt-C++笔记之QThread使用
- 一:Qt中几种多线程方法
- 1.1. 使用 `QThread` 和 Lambda
- 1.2. 使用 `QThread::create()`
- 1.3. 继承 `QThread` 并重写 `run()`
- 1.4. 使用 `QObject` 派生类与 `QThread`
- 1.5. 使用 `QtConcurrent::run()`
- 总结
- 二.方法一:继承QThread并重写其run()方法
- 2.1.代码一:运行全局纯函数
- 2.2.代码二:运行全局纯函数
- 2.3.代码一和代码二的区别
- 2.3.1 带`input`参数的构造函数
- 2.3.2. 带`input`和`parent`参数的构造函数
- 2.3.3 总结
- 2.4.代码三:直接在run()方法中写运行逻辑
- 2.5.代码四:直接在run()方法中写运行逻辑,并在QCoreApplication::quit()前执行一些打印
- 2.6.代码五:通过继承QThread的方式来运行一个纯函数,并将参数通过类的成员变量传递给这个函数
- 2.7.代码六:通过继承QThread的方式来运行一个纯函数,并将参数通过类的成员变量传递给这个函数
- 三.方法二:将任务放在QObject派生类中,并`moveToThread()`在QThread中运行这个QObject
- 3.1. 代码一:运行成员函数例程,让 Worker 类继承自 QObject,然后使用一个 QThread 实例来在另一个线程中运行这个 Worker 对象
- 3.2. 代码二:运行成员函数例程,让 Worker 类继承自 QObject,然后使用一个 QThread 实例来在另一个线程中运行这个 Worker 对象
- 四.方法三:直接使用 QThread 和 Lambda
- 五.方法四:`QThread::create(Qt 5.10 及以上)`
- 5.1.提要
- 5.1.1.QThread::create()代码一
- 5.1.2.QThread::create()代码二
- 六.方法五:`QtConcurrent::run()`
一:Qt中几种多线程方法
五种不同的多线程实现方法在Qt中的特点和适用场景:
方法 | 描述 | 优点 | 缺点 | 适用场景 |
---|---|---|---|---|
直接使用 QThread 和 Lambda | 使用 QThread 对象和信号槽连接一个lambda表达式来执行代码。 | 简单快速;灵活定义启动行为。 | 精细控制线程较少;线程复用不便。 | 快速实现简单的后台任务,不需要复用线程。 |
使用 QThread::create() | 使用 QThread::create() 创建并自动启动线程来执行函数或lambda。 | 极简代码;自动线程管理。 | 对线程的控制较为有限;不易于复用。 | 适合快速启动一次性后台任务,不需要线程间交互。 |
继承 QThread 并重写 run() | 通过继承 QThread 并重写其 run() 方法来定义线程行为。 | 完全控制线程行为;能够处理复杂逻辑。 | 实现复杂;易误用(如直接操作GUI)。 | 需要精细控制线程行为或有复杂线程逻辑的场景。 |
使用 QObject 派生类与 QThread | 创建 QObject 派生类,并在移至 QThread 的对象上执行任务。 | 分离工作和线程管理;完全的信号和槽支持。 | 设置相对繁琐;代码量较多。 | 需要线程频繁与主线程通信或任务较为复杂的场景。 |
使用 QtConcurrent::run() | 使用Qt并发模块的 QtConcurrent::run() 在线程池中运行函数或成员函数。 | 简单易用;自动管理线程池;减少资源消耗。 | 对线程控制较少;不适合特定线程管理需求。 | 适用于执行独立的并发任务,不需要细粒度线程控制。 |
1.1. 使用 QThread
和 Lambda
这种方法直接使用 QThread
的实例,并通过信号和槽系统将lambda表达式绑定至 QThread::started
信号。这种方法允许灵活地定义线程开始时执行的代码,而不需创建额外的类或使用 QtConcurrent
。它适合快速简单的线程使用,尤其是当你不需要频繁与主线程通信或管理复杂的线程生命周期时。
1.2. 使用 QThread::create()
QThread::create()
是Qt 5.10引入的一个便捷函数,它基本上是创建线程与绑定任务的简化版。你只需要提供一个函数或lambda表达式,QThread::create()
会自动创建线程并在启动线程时执行这个函数。
auto thread = QThread::create([](){
// 执行一些任务
});
thread->start();
这种方法的优点是极其简单,但它通常不适用于需要复杂线程管理或多次复用线程的场景。
1.3. 继承 QThread
并重写 run()
这是一种更传统的方法,通过继承 QThread
并重写其 run()
方法实现。这种方法提供了最大的灵活性,允许你控制线程的准确行为,但也需要更多的代码和复杂的错误处理。
class WorkerThread : public QThread
{
protected:
void run() override {
// 执行任务
}
};
1.4. 使用 QObject
派生类与 QThread
这是Qt推荐的多线程使用方式。你创建一个 QObject
派生类来封装工作任务,然后将这个对象的实例移动到 QThread
中。
class Worker : public QObject
{
Q_OBJECT
public slots:
void doWork() {
// 执行任务
}
};
QThread *thread = new QThread();
Worker *worker = new Worker();
worker->moveToThread(thread);
connect(thread, &QThread::started, worker, &Worker::doWork);
thread->start();
这种方法适合需要线程与主线程频繁交互的场景,因为它完全兼容Qt的信号与槽机制。
1.5. 使用 QtConcurrent::run()
QtConcurrent::run()
是处理并发运算的另一种高级方法,它可以非常方便地在后台线程池中运行函数或成员函数。
QtConcurrent::run([](){
// 执行某些操作
});
这种方法非常适合不需要细粒度控制线程行为的场景,且可以自动管理线程池,避免创建和销毁线程的开销。
总结
- 直接使用
QThread
和 Lambda:适合快速、一次性的简单后台任务。 - 使用
QThread::create()
:简化版的线程创建和任务绑定,适合不需要复用线程的场景。 - 继承
QThread
:适用于需要完全控制线程行为的复杂场景。 - 使用
QObject
派生类与QThread
:Qt推荐的方式,适合需要线程间频繁通信的场景。 - 使用
QtConcurrent::run()
:适用于简单并发任务,自动线程池管理,减少资源消耗。
二.方法一:继承QThread并重写其run()方法
2.1.代码一:运行全局纯函数
#include <QCoreApplication>
#include <QThread>
#include <QDebug>
// 纯函数的定义,这个函数只进行计算并返回结果,不涉及任何的状态修改
int pureFunction(int x) {
return x * x;
}
// 创建一个线程类,用于运行纯函数
class WorkerThread : public QThread {
public:
WorkerThread(int input) : inputValue(input) {}
protected:
void run() override {
int result = pureFunction(inputValue);
qDebug() << "计算结果:" << result;
}
private:
int inputValue;
};
int main(int argc, char *argv[]) {
QCoreApplication a(argc, argv);
// 初始化一个线程对象,传入参数5
WorkerThread thread(5);
// 连接线程的finished信号到QCoreApplication的quit槽,确保应用程序在线程结束后退出
QObject::connect(&thread, &QThread::finished, &a, &QCoreApplication::quit);
// 启动线程
thread.start();
// 进入事件循环
return a.exec();
}
运行
计算结果: 25
2.2.代码二:运行全局纯函数
#include <QCoreApplication>
#include <QThread>
#include <QDebug>
// 全局纯函数
void globalPureFunction(int param) {
qDebug() << "运行在线程:" << QThread::currentThreadId();
qDebug() << "接收到的参数:" << param;
// 模拟一些处理过程
QThread::sleep(2); // 假装我们在做一些耗时的工作
qDebug() << "处理完成";
}
// 线程类
class WorkerThread : public QThread {
int m_param;
public:
WorkerThread(int param, QObject *parent = nullptr) : QThread(parent), m_param(param) {}
protected:
void run() override {
globalPureFunction(m_param); // 在新线程中调用全局纯函数
}
};
// 主函数
int main(int argc, char *argv[]) {
QCoreApplication a(argc, argv);
int inputValue = 42; // 示例参数值
WorkerThread *thread = new WorkerThread(inputValue);
thread->start(); // 启动线程
QObject::connect(thread, &QThread::finished, thread, &QObject::deleteLater);
QObject::connect(thread, &QThread::finished, &a, &QCoreApplication::quit);
return a.exec();
}
运行
运行在线程: 0x7fd2ea57f700
接收到的参数: 42
处理完成
2.3.代码一和代码二的区别
在Qt中,处理QThread
和其他继承自QObject
的类时,构造函数中的parent
参数非常关键,它影响对象的内存管理和事件传递机制。让我们比较一下这两种构造函数的定义和它们的实际用途:
2.3.1 带input
参数的构造函数
WorkerThread(int input) : inputValue(input) {}
这个构造函数只接受一个input
参数,并初始化成员变量inputValue
。这里没有显式地处理parent
参数:
- 作用:初始化
inputValue
。 - 内存管理:这个
WorkerThread
对象的生命周期需要显式管理(比如通过在堆上创建和删除,或者确保其作用域在使用中不会结束),因为没有父对象来自动管理它。 - 适用场景:当你不需要将线程对象的生命周期与其他Qt对象关联时,或者当你想要通过代码显式管理线程的生命周期时使用。
2.3.2. 带input
和parent
参数的构造函数
WorkerThread(int input, QObject *parent = nullptr) : QThread(parent), inputValue(input) {}
这个构造函数接受一个input
参数和一个可选的parent
参数,默认为nullptr
。它在初始化列表中调用了QThread
的构造函数,传递了parent
参数:
- 作用:初始化
inputValue
并设置父对象。 - 内存管理:如果指定了父对象(
parent
不为nullptr
),这个WorkerThread
对象的生命周期将由其父对象自动管理(父对象销毁时,它也会被销毁)。如果parent
为nullptr
,则其生命周期需要手动管理。 - 适用场景:当你希望线程的生命周期与某个Qt对象(如窗口或其他组件)绑定时使用。这样可以简化内存管理,使线程的生命周期与其父对象相匹配。
2.3.3 总结
添加parent
参数的版本提供了更灵活的内存管理选项,允许线程对象以树形层次结构中的一部分被管理,这对于复杂的Qt应用程序来说非常有用。没有parent
参数的版本则简单、直接,更适合生命周期管理相对明确或简单的场合。在实际应用中,选择哪种方式取决于你的具体需求和线程管理策略。
2.4.代码三:直接在run()方法中写运行逻辑
#include <QCoreApplication>
#include <QThread>
#include <iostream>
// WorkerThread 类,继承自 QThread
class WorkerThread : public QThread
{
public:
WorkerThread(QObject *parent = nullptr) : QThread(parent) {}
// 重载 run 方法
void run() override {
// 纯函数任务内容
for (int i = 0; i < 10; ++i) {
std::cout << "工作线程运行中: " << i << std::endl;
QThread::sleep(1); // 模拟耗时操作,每次循环暂停1秒
}
std::cout << "工作线程结束运行。" << std::endl;
}
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
// 创建 WorkerThread 对象
WorkerThread thread;
// 连接线程完成信号到退出槽,确保线程完成后应用程序退出
QObject::connect(&thread, &WorkerThread::finished, &a, &QCoreApplication::quit);
// 启动线程
thread.start();
// 进入 Qt 事件循环
return a.exec();
}
运行
工作线程运行中: 0
工作线程运行中: 1
工作线程运行中: 2
工作线程运行中: 3
工作线程运行中: 4
工作线程运行中: 5
工作线程运行中: 6
工作线程运行中: 7
工作线程运行中: 8
工作线程运行中: 9
工作线程结束运行。
2.5.代码四:直接在run()方法中写运行逻辑,并在QCoreApplication::quit()前执行一些打印
#include <QCoreApplication>
#include <QThread>
#include <QDebug>
// 纯函数,作为线程任务
void runTask() {
for (int i = 0; i < 10; ++i) {
QThread::sleep(1); // 模拟耗时任务
qDebug() << "工作在线程 " << QThread::currentThreadId() << " 中执行: " << i;
}
}
// 自定义的线程类
class TaskThread : public QThread {
Q_OBJECT // 添加 Q_OBJECT 宏以支持信号和槽
public:
TaskThread(QObject *parent = nullptr) : QThread(parent) {}
protected:
void run() override {
runTask(); // 在新线程中运行纯函数
emit taskCompleted(); // 发射任务完成信号
}
signals:
void taskCompleted();
};
int main(int argc, char *argv[]) {
QCoreApplication a(argc, argv);
TaskThread thread;
QObject::connect(&thread, &TaskThread::taskCompleted, &a, [&]() {
qDebug() << "任务完成,信号在主线程 " << QThread::currentThreadId() << " 中被处理";
QCoreApplication::quit(); // 完成后退出程序
});
qDebug() << "主线程ID: " << QThread::currentThreadId();
thread.start(); // 启动线程
return a.exec();
}
#include "main.moc" // 如果你不是使用 qmake,确保 moc 处理这个文件
运行
主线程ID: 0x7f7e73b0d780
工作在线程 0x7f7e6ef9d700 中执行: 0
工作在线程 0x7f7e6ef9d700 中执行: 1
工作在线程 0x7f7e6ef9d700 中执行: 2
工作在线程 0x7f7e6ef9d700 中执行: 3
工作在线程 0x7f7e6ef9d700 中执行: 4
工作在线程 0x7f7e6ef9d700 中执行: 5
工作在线程 0x7f7e6ef9d700 中执行: 6
工作在线程 0x7f7e6ef9d700 中执行: 7
工作在线程 0x7f7e6ef9d700 中执行: 8
工作在线程 0x7f7e6ef9d700 中执行: 9
任务完成,信号在主线程 0x7f7e73b0d780 中被处理
2.6.代码五:通过继承QThread的方式来运行一个纯函数,并将参数通过类的成员变量传递给这个函数
#include <QCoreApplication>
#include <QThread>
#include <QDebug>
// 纯函数,有传参
int square(int x) {
return x * x;
}
// 继承QThread的类,重写run()函数
class MyThread : public QThread {
protected:
void run() override {
// 调用纯函数,并传参
int result = square(x);
qDebug() << "Result:" << result;
}
public:
int x;
};
int main(int argc, char *argv[]) {
QCoreApplication a(argc, argv);
// 创建MyThread对象
MyThread thread;
// 在main函数中声明参数,并传入MyThread对象
int param = 10;
thread.x = param;
// 启动MyThread对象
thread.start();
// 等待MyThread对象结束
thread.wait();
return a.exec();
}
运行
Result: 100
2.7.代码六:通过继承QThread的方式来运行一个纯函数,并将参数通过类的成员变量传递给这个函数
#include <QCoreApplication>
#include <QThread>
#include <QDebug>
// 纯函数的声明
void pureFunction(int a, int b);
// 继承自 QThread 的类
class WorkerThread : public QThread {
public:
// 构造函数
WorkerThread(int a, int b, QObject *parent = nullptr) : QThread(parent), m_a(a), m_b(b) {}
protected:
// 重写 run 方法
void run() override {
// 在新线程中调用纯函数
qDebug() << "线程开始执行";
pureFunction(m_a, m_b);
qDebug() << "线程执行完成";
}
private:
int m_a, m_b; // 成员变量,用于存储传递给纯函数的参数
};
// 纯函数的实现
void pureFunction(int a, int b) {
// 执行一些计算或处理
int result = a + b;
qDebug() << "纯函数执行结果:" << result;
}
int main(int argc, char *argv[]) {
QCoreApplication a(argc, argv);
// 创建 WorkerThread 对象,传递参数给纯函数
WorkerThread thread(10, 20);
// 启动线程
thread.start();
// 等待线程执行完成
thread.wait();
return a.exec();
}
运行
线程开始执行
纯函数执行结果: 30
线程执行完成
三.方法二:将任务放在QObject派生类中,并moveToThread()
在QThread中运行这个QObject
3.1. 代码一:运行成员函数例程,让 Worker 类继承自 QObject,然后使用一个 QThread 实例来在另一个线程中运行这个 Worker 对象
// 包含必要的头文件
#include <QCoreApplication>
#include <QThread>
#include <QDebug>
// Worker 类定义
class Worker : public QObject {
Q_OBJECT
public:
Worker() {}
virtual ~Worker() {}
public slots:
void process() {
// 输出当前线程信息
qDebug() << "Worker thread running in thread:" << QThread::currentThreadId();
// 模拟耗时操作
QThread::sleep(3);
qDebug() << "Worker process completed.";
emit finished();
}
signals:
void finished();
};
int main(int argc, char *argv[]) {
QCoreApplication a(argc, argv);
// 创建线程对象
QThread workerThread;
Worker worker;
// 将 worker 对象移动到新建的线程
worker.moveToThread(&workerThread);
// 连接信号和槽
QObject::connect(&workerThread, &QThread::started, &worker, &Worker::process);
QObject::connect(&worker, &Worker::finished, &workerThread, &QThread::quit);
QObject::connect(&worker, &Worker::finished, &worker, &Worker::deleteLater);
QObject::connect(&workerThread, &QThread::finished, &workerThread, &QThread::deleteLater);
// 启动线程
workerThread.start();
// 运行事件循环
int result = a.exec();
// 等待线程结束
workerThread.wait();
return result;
}
#include "main.moc"
运行
Worker thread running in thread: 0x7f1943a36700
Worker process completed.
double free or corruption (out)
15:43:36: The program has unexpectedly finished.
3.2. 代码二:运行成员函数例程,让 Worker 类继承自 QObject,然后使用一个 QThread 实例来在另一个线程中运行这个 Worker 对象
#include <QCoreApplication>
#include <QThread>
#include <QDebug>
// Worker类,负责执行实际的计算
class Worker : public QObject {
Q_OBJECT
public:
Worker(int a, int b) : m_a(a), m_b(b) {}
public slots:
void process() {
int result = m_a + m_b; // 简单的加法计算
qDebug() << "计算结果:" << result;
emit finished();
}
signals:
void finished();
private:
int m_a;
int m_b;
};
int main(int argc, char *argv[]) {
QCoreApplication a(argc, argv);
int value1 = 5, value2 = 3;
Worker *worker = new Worker(value1, value2);
QThread *thread = new QThread;
// 将worker移动到线程
worker->moveToThread(thread);
// 连接信号和槽
QObject::connect(thread, &QThread::started, worker, &Worker::process);
QObject::connect(worker, &Worker::finished, thread, &QThread::quit);
QObject::connect(worker, &Worker::finished, worker, &QObject::deleteLater);
QObject::connect(thread, &QThread::finished, thread, &QObject::deleteLater);
QObject::connect(thread, &QThread::finished, &a, &QCoreApplication::quit); // 确保应用退出
// 启动线程
thread->start();
return a.exec();
}
#include "main.moc"
运行
计算结果: 8
四.方法三:直接使用 QThread 和 Lambda
#include <QCoreApplication>
#include <QThread>
#include <QDebug>
// 纯函数定义,用于计算两个整数的和
int add(int a, int b) {
return a + b;
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
// 创建线程对象
QThread* thread = new QThread;
int x = 3, y = 4;
// 将任务移至线程,使用lambda表达式
QObject::connect(thread, &QThread::started, [=]() mutable {
int result = add(x, y);
qDebug() << "The sum of" << x << "and" << y << "is" << result;
thread->quit(); // 线程任务完成,请求退出线程
});
// 清理线程资源
QObject::connect(thread, &QThread::finished, thread, &QObject::deleteLater);
// 启动线程
thread->start();
return a.exec();
}
运行
The sum of 3 and 4 is 7
五.方法四:QThread::create(Qt 5.10 及以上)
5.1.提要
-
直接在 QThread类中没有提供直接创建并启动线程执行特定函数的方法(例如 QThread::create 是 C++11 后Qt 5.10 添加的功能,如果您使用的Qt版本较旧,这一功能可能不可用)
-
在Qt中使用QThread来运行一个全局纯函数是一个比较通用的任务,在Qt中,通常通过继承QThread并重写run()方法来实现这一点,但使用QThread的能力来直接启动一个线程执行我们的函数。
5.1.1.QThread::create()代码一
#include <QCoreApplication>
#include <QThread>
#include <QDebug>
void performCalculation(int a, int b) {
int result = a + b;
qDebug() << "计算结果:" << result;
}
int main(int argc, char *argv[]) {
QCoreApplication app(argc, argv);
QThread *thread = QThread::create(performCalculation, 5, 3);
QObject::connect(thread, &QThread::finished, thread, &QObject::deleteLater);
thread->start();
return app.exec();
}
5.1.2.QThread::create()代码二
#include <QCoreApplication>
#include <QThread>
#include <QDebug>
// 全局纯函数
void performCalculation(int a, int b) {
int result = a + b; // 简单的加法计算
qDebug() << "计算结果:" << result;
}
// 线程执行的函数
void runInThread(int a, int b) {
// 创建线程对象
QThread* thread = QThread::create([=](){
performCalculation(a, b);
});
// 连接线程结束信号到删除槽,确保资源被清理
QObject::connect(thread, &QThread::finished, thread, &QObject::deleteLater);
// 启动线程
thread->start();
}
int main(int argc, char *argv[]) {
QCoreApplication a(argc, argv);
// 在线程中执行全局函数
runInThread(5, 3);
return a.exec();
}
六.方法五:QtConcurrent::run()
#include <QCoreApplication>
#include <QThread>
#include <QDebug>
#include <QtConcurrent>
// 全局纯函数
void performCalculation(int a, int b) {
int result = a + b; // 简单的加法计算
qDebug() << "在线程" << QThread::currentThreadId() << "计算结果:" << result;
}
int main(int argc, char *argv[]) {
QCoreApplication app(argc, argv);
// 使用QtConcurrent运行全局函数
int value1 = 5, value2 = 3;
QFuture<void> future = QtConcurrent::run(performCalculation, value1, value2);
// 等待任务完成
future.waitForFinished();
return app.exec();
}