1.概要
2.内容
在 Qt 中,创建线程主要有以下几种方式:
1. 派生于 QThread
这是 Qt 中最常用的线程创建方式。你可以通过继承 QThread
类并重写其 run()
方法来定义线程的执行内容。然后,通过调用 start()
方法来启动线程。
class MyThread : public QThread
{
Q_OBJECT
protected:
void run() override
{
// 在这里执行你的代码
}
};
// 使用
MyThread *myThread = new MyThread(this);
myThread->start();
2. 使用 Qt 的 QRunnable 类
QRunnable
是一个轻量级的抽象,用于表示一个可以并行执行的任务。你可以将 QRunnable
对象提交给 QThreadPool
来异步执行。
class MyRunnable : public QRunnable
{
public:
void run() override
{
// 在这里执行你的代码
}
};
// 使用
MyRunnable *myRunnable = new MyRunnable();
QThreadPool::globalInstance()->start(myRunnable);
3. 使用 Qt 的 moveToThread 方法
这种方法不需要子类化 QThread
,只需要子类化一个 QObject
。你可以使用 moveToThread()
方法将 QObject
实例移动到一个新的线程中,然后启动该线程。
class Worker : public QObject
{
Q_OBJECT
public slots:
void doWork(const QString ¶meter)
{
// 在这里执行你的代码
}
};
// 使用
QThread workerThread;
Worker worker;
workerThread.start();
worker.moveToThread(&workerThread);
connect(&workerThread, &QThread::finished, &worker, &QObject::deleteLater);
// 连接信号和槽来执行工作
4. 使用 QtConcurrent::run()
QtConcurrent::run()
允许你在一个新的线程中执行一个函数。这是一种便捷的方式来运行一个函数而不阻塞主线程。
void myFunction()
{
// 在这里执行你的代码
}
// 使用
QtConcurrent::run(myFunction);
5. 使用 C++11 的标准线程库
虽然这不是 Qt 特有的方法,但你可以使用 C++11(或更高版本)的标准线程库来创建和管理线程。例如,使用 std::thread
类来创建线程。这种方式与 Qt 的集成可能不如使用 Qt 自带的线程类那么紧密,但它提供了更多的灵活性和对底层线程的直接控制。
#include <thread>
void myFunction()
{
// 在这里执行你的代码
}
// 使用
std::thread myThread(myFunction);
myThread.join(); // 等待线程完成
总结
以上就是在 Qt 中创建线程的几种主要方式。你可以根据具体的需求和场景选择合适的方法。在选择时,应考虑到任务的性质、执行频率、持续时间以及资源使用情况等因素。同时,务必注意线程同步和通信的问题,以避免数据竞争和其他并发问题。
3.关联知识
1.qt 创建一个线程
在 Qt 中创建一个线程通常涉及几个步骤,包括子类化 QThread
,重写其 run()
方法,并在需要时启动线程。下面是一个简单的例子,展示了如何在 Qt 中创建和使用一个线程:
-
子类化
QThread
:
首先,你需要创建一个继承自QThread
的类,并重写其run()
方法。run()
方法将包含在新线程中执行的代码。 -
实现
run()
方法:
在run()
方法中,编写你希望在新线程中执行的代码。 -
启动线程:
创建你的QThread
子类的实例,并调用其start()
方法来启动线程。
下面是一个简单的示例,演示了这些步骤:
#include <QCoreApplication>
#include <QThread>
#include <QDebug>
// 1. 子类化 QThread
class MyThread : public QThread
{
Q_OBJECT
protected:
// 2. 实现 run() 方法
void run() override {
for (int i = 0; i < 5; ++i) {
qDebug() << "Thread running:" << i;
QThread::sleep(1); // 使线程休眠一秒
}
}
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
// 3. 创建并启动线程
MyThread thread;
thread.start();
// 等待线程完成(可选)
thread.wait();
return a.exec();
}
#include "main.moc" // 如果你使用的是单个文件的方式,需要这一行来生成 MOC 代码
注意事项
-
线程同步:
如果多个线程需要访问共享数据,你需要使用适当的同步机制(如互斥锁QMutex
)来避免数据竞争和条件竞争。 -
线程间通信:
Qt 提供了信号和槽机制,这是线程间通信的一种有效方式。注意,当向另一个线程的对象发送信号时,槽函数将在接收对象的线程中执行。 -
QObject 的线程相关性:
QObject
及其子类不是线程安全的,这意味着它们的实例应该只在创建它们的线程中使用。如果需要跨线程使用QObject
,考虑使用QCoreApplication::postEvent()
或信号槽机制。 -
管理线程生命周期:
确保在程序退出前适当地管理和终止线程。如果线程在应用程序退出时仍在运行,它可能会导致未定义行为或程序崩溃。
通过遵循这些步骤和注意事项,你可以在 Qt 应用程序中安全和有效地使用多线程。
2.使用 Qt 的 moveToThread 方法
moveToThread
是 Qt 中用于将对象移动到另一个线程的方法。这在需要执行耗时操作而不阻塞主线程(通常是 GUI 线程)时非常有用。下面是一个简单的例子,演示如何使用 moveToThread
方法将工作负载移到另一个线程。
示例代码
假设我们有一个耗时的任务,我们希望在一个单独的线程中执行它。我们可以按照以下步骤来实现:
- 创建一个继承自
QObject
的工作类,并在其中定义耗时任务。 - 创建一个
QThread
对象。 - 将工作对象移动到新线程。
- 在新线程中启动耗时任务。
以下是完整的示例代码:
#include <QCoreApplication>
#include <QThread>
#include <QObject>
#include <QDebug>
#include <QTimer>
// 工作类,继承自 QObject
class Worker : public QObject {
Q_OBJECT
public slots:
void doWork() {
// 耗时任务,例如模拟一个长时间运行的任务
for (int i = 0; i < 5; ++i) {
qDebug() << "Working in thread:" << QThread::currentThread();
QThread::sleep(1); // 模拟耗时操作
}
emit workFinished();
}
signals:
void workFinished();
};
int main(int argc, char *argv[]) {
QCoreApplication app(argc, argv);
// 创建工作对象
Worker worker;
// 创建并启动新线程
QThread workerThread;
worker.moveToThread(&workerThread);
// 连接信号和槽
QObject::connect(&workerThread, &QThread::started, &worker, &Worker::doWork);
QObject::connect(&worker, &Worker::workFinished, &workerThread, &QThread::quit);
QObject::connect(&workerThread, &QThread::finished, &workerThread, &QObject::deleteLater);
// 启动线程
workerThread.start();
// 主线程继续执行其他任务
qDebug() << "Main thread:" << QThread::currentThread();
return app.exec();
}
#include "main.moc"
代码解释
- 创建工作类:
Worker
类继承自QObject
,并在其中定义了一个doWork
槽函数,模拟一个耗时任务。doWork
函数中,我们使用QThread::sleep(1)
来模拟耗时操作。- 定义了一个
workFinished
信号,当任务完成时发射该信号。
- 创建工作对象和新线程:
- 在
main
函数中,创建Worker
对象和QThread
对象。
- 在
- 移动工作对象到新线程:
- 使用
moveToThread
方法将Worker
对象移动到workerThread
线程。
- 使用
- 连接信号和槽:
- 连接
workerThread
的started
信号到worker
的doWork
槽,以便线程启动时开始工作。 - 连接
worker
的workFinished
信号到workerThread
的quit
槽,以便工作完成时线程退出。 - 连接
workerThread
的finished
信号到其自身的deleteLater
槽,以便线程结束时自动删除线程对象。
- 连接
- 启动线程:
- 调用
workerThread.start()
启动新线程。
- 调用
- 主线程继续执行:
- 主线程可以继续执行其他任务,而不会被耗时操作阻塞。
注意事项
- 跨线程通信必须使用信号和槽机制,而不是直接调用槽函数。
- 确保在对象移动到另一个线程之前,不要对其进行任何进一步的直接操作。
- 线程结束后,记得清理资源,避免内存泄漏。
这个示例展示了如何使用 moveToThread
将耗时任务移到后台线程,从而保持主线程的响应性。
3.使用 Qt 的 moveToThread 方法移动到一个线程中,如何给这个线程添加任务
在 Qt 中,使用 moveToThread
方法将一个对象移动到一个新的线程后,你可以通过信号和槽机制来给这个线程中的对象添加任务。这是因为直接跨线程调用槽函数是不安全的,可能会导致线程竞争和数据不一致等问题。因此,Qt 强制要求所有的跨线程通信都必须通过信号和槽来进行。
下面是一个扩展的示例,展示了如何给一个已经移动到新线程的对象添加任务:
#include <QCoreApplication>
#include <QThread>
#include <QObject>
#include <QDebug>
#include <QTimer>
// 工作类,继承自 QObject
class Worker : public QObject {
Q_OBJECT
public slots:
void doWork(const QString &taskDescription) {
// 执行传入的任务
qDebug() << "Working on task:" << taskDescription << "in thread:" << QThread::currentThread();
// 模拟耗时操作
QThread::sleep(2);
emit workFinished();
}
signals:
void workFinished();
};
int main(int argc, char *argv[]) {
QCoreApplication app(argc, argv);
// 创建工作对象
Worker worker;
// 创建并启动新线程
QThread workerThread;
worker.moveToThread(&workerThread);
// 连接信号和槽
QObject::connect(&workerThread, &QThread::started, [&worker]() {
worker.doWork("Initial Task");
});
QObject::connect(&worker, &Worker::workFinished, &workerThread, &QThread::quit);
QObject::connect(&workerThread, &QThread::finished, &workerThread, &QObject::deleteLater);
// 启动线程
workerThread.start();
// 在主线程中等待一段时间后添加新任务
QTimer::singleShot(3000, [&worker]() {
// 注意:这里使用 lambda 表达式是因为我们需要在主线程中发送信号
// 而 doWork 需要在 workerThread 中执行
QMetaObject::invokeMethod(&worker, "doWork", Q_ARG(QString, "New Task from Main Thread"));
});
// 主线程继续执行其他任务
qDebug() << "Main thread:" << QThread::currentThread();
return app.exec();
}
#include "main.moc"
在这个示例中,我们做了以下几点修改:
- 给
Worker
类添加了一个带参数的doWork
槽函数,这样我们就可以传递任务描述给它。 - 在
main
函数中,我们使用QTimer::singleShot
来在主线程中延迟 3 秒后发送一个新任务给worker
对象。这里使用了QMetaObject::invokeMethod
来确保跨线程调用是安全的。 - 我们连接了
workerThread
的started
信号到一个 lambda 表达式,这个表达式在线程启动时立即调用worker
的doWork
方法,并传递一个初始任务描述。
注意,虽然 QMetaObject::invokeMethod
可以在这里用于跨线程调用,但通常建议在设计时就通过信号和槽来规划好跨线程通信,而不是在运行时动态调用槽函数。这样做可以使代码更加清晰和易于维护。
4.QThread::quit
QThread::quit()
是 Qt 框架中 QThread
类的一个成员函数,用于请求线程退出。当调用这个函数时,线程并不会立即停止,而是会执行完当前事件循环中的所有事件后才退出。如果线程中没有事件循环(即没有调用 exec()
),那么 quit()
将不会起任何作用,线程会继续执行直到它的 run()
方法完成。
在使用 QThread
时,通常的做法是:
- 创建一个继承自
QThread
的类,并重写run()
方法以包含线程应该执行的代码。 - 或者,更常见且推荐的做法是,创建一个
QObject
子类来包含工作逻辑,然后将这个对象移动到一个QThread
实例中。 - 在需要线程退出时,从适当的线程(通常是主线程或工作对象所在的线程)调用
quit()
。
当 quit()
被调用后,如果线程中有事件循环,它会退出该循环,并且如果线程是通过 QThread
的 start()
方法启动的,那么线程的 run()
方法也会结束,从而导致线程退出。
重要的是要注意,quit()
只是请求线程退出,它不会强制线程立即停止。如果线程中有长时间运行的任务或阻塞调用,那么线程可能不会立即响应 quit()
请求。在这种情况下,可能需要额外的机制来确保线程能够安全地中断和退出。
此外,QThread::quit()
与 QThread::exit()
是不同的。exit()
会立即退出线程的事件循环,并且可以选择性地返回一个退出码,而 quit()
只是请求退出事件循环,不涉及退出码。在大多数情况下,使用 quit()
就足够了,除非你需要从线程退出时传递特定的状态信息。
5.使用 Qt 的 moveToThread 方法创建线程,在另一个线程不断的用信号,给这个线程添加任务
使用 Qt 的 moveToThread
方法可以将对象移动到另一个线程中运行,而利用信号和槽机制可以在不同线程之间安全地传递任务和数据。以下是一个示例,展示了如何在一个线程中不断地用信号给另一个线程添加任务。
首先,确保你已经安装了 Qt 并设置好开发环境。以下是一个完整的示例代码:
#include <QCoreApplication>
#include <QThread>
#include <QObject>
#include <QDebug>
#include <QTimer>
// Worker 类在另一个线程中执行任务
class Worker : public QObject {
Q_OBJECT
public slots:
void doWork(const QString ¶meter) {
qDebug() << "Working in thread:" << QThread::currentThread() << "with parameter:" << parameter;
// 模拟长时间任务
QThread::sleep(2);
qDebug() << "Finished work in thread:" << QThread::currentThread();
}
};
// MainObject 类在主线程中,负责发送信号
class MainObject : public QObject {
Q_OBJECT
public:
MainObject() {
// 创建一个新的线程
workerThread = new QThread;
// 创建 Worker 对象
worker = new Worker;
// 将 Worker 对象移动到新的线程中
worker->moveToThread(workerThread);
// 启动线程
workerThread->start();
// 连接信号和槽
connect(this, &MainObject::operate, worker, &Worker::doWork);
connect(workerThread, &QThread::finished, worker, &QObject::deleteLater);
}
~MainObject() {
// 停止线程
workerThread->quit();
workerThread->wait();
delete workerThread;
}
signals:
void operate(const QString &);
public:
QThread *workerThread;
Worker *worker;
};
int main(int argc, char *argv[]) {
QCoreApplication a(argc, argv);
MainObject mainObject;
// 使用 QTimer 每隔一段时间发送信号
QTimer timer;
QObject::connect(&timer, &QTimer::timeout, [&mainObject]() {
static int counter = 0;
mainObject.emit operate(QString("Task %1").arg(++counter));
});
timer.start(1000); // 每隔1秒发送一次信号
return a.exec();
}
#include "main.moc"
代码说明
- Worker 类:
- 定义一个槽
doWork
,用于在另一个线程中执行任务。 - 使用
qDebug
打印当前线程信息和任务参数。 - 模拟一个长时间任务(使用
QThread::sleep(2)
)。
- 定义一个槽
- MainObject 类:
- 创建一个新的
QThread
对象和一个Worker
对象。 - 使用
moveToThread
将Worker
对象移动到新的线程中。 - 启动线程。
- 连接
MainObject::operate
信号到Worker::doWork
槽。 - 在析构函数中,确保线程正确停止和清理。
- 创建一个新的
- main 函数:
- 创建
QCoreApplication
对象。 - 创建
MainObject
对象。 - 使用
QTimer
每隔1秒发送一个信号,调用MainObject::operate
,传递任务参数。
- 创建
注意事项
- 使用
moveToThread
后,对象的所有槽都会在新的线程中执行。 - 确保线程间的信号和槽连接是正确的,尤其是在主线程和工作线程之间传递数据时。
- 在多线程编程中,要注意线程安全和对象生命周期管理,避免内存泄漏和访问冲突。
这个示例展示了如何使用 Qt 的信号和槽机制以及 moveToThread
方法在不同线程之间传递任务和数据,希望对你有所帮助。
6.QObject::deleteLater
QObject::deleteLater()
是 Qt 框架中的一个成员函数,属于 QObject
类。这个函数的作用是安排调用此函数的对象在事件循环的下一个“空闲”时间被删除。换句话说,它不会立即删除对象,而是将对象的删除操作推迟到事件循环的下一个迭代中,从而确保删除操作在安全的上下文中执行。
使用场景
deleteLater()
通常用于以下场景:
-
跨线程删除对象:当对象在一个线程中被创建,但需要在另一个线程中被删除时,直接删除可能会导致线程安全问题。使用
deleteLater()
可以确保对象在正确的线程和事件循环中被删除。 -
延迟删除:有时候,你可能不希望立即删除一个对象,而是希望在某个事件或操作完成后才删除。这时,可以使用
deleteLater()
来安排对象的延迟删除。 -
避免内存泄漏:在复杂的应用程序中,确保所有对象都被正确删除以避免内存泄漏是非常重要的。使用
deleteLater()
可以帮助你管理对象的生命周期,确保对象在不再需要时被删除。
注意事项
-
事件循环:
deleteLater()
依赖于事件循环来执行删除操作。如果对象所在线程没有事件循环,那么deleteLater()
将不会起作用。因此,在调用deleteLater()
之前,请确保对象所在线程有一个正在运行的事件循环。 -
对象状态:在调用
deleteLater()
之后,对象仍然可以被访问,直到事件循环实际执行删除操作为止。因此,在调用deleteLater()
后立即访问对象的状态或成员变量可能是不安全的。 -
父对象与子对象:在 Qt 中,父对象负责删除其子对象。如果一个对象被设置为另一个对象的子对象,并且父对象被删除,那么子对象也会被自动删除。在这种情况下,通常不需要对子对象调用
deleteLater()
。 -
多次调用:对同一个对象多次调用
deleteLater()
是安全的。Qt 会确保对象只被删除一次。
示例代码
以下是一个简单的示例,展示了如何使用 deleteLater()
:
#include <QCoreApplication>
#include <QObject>
#include <QTimer>
#include <QDebug>
class MyObject : public QObject {
Q_OBJECT
public slots:
void scheduleDeletion() {
qDebug() << "Scheduling deletion...";
deleteLater(); // 安排对象在事件循环的下一个空闲时间被删除
}
void doSomething() {
qDebug() << "Doing something...";
// ... 执行一些操作 ...
}
};
int main(int argc, char *argv[]) {
QCoreApplication a(argc, argv);
MyObject obj;
QTimer::singleShot(1000, &obj, &MyObject::doSomething); // 1秒后执行doSomething
QTimer::singleShot(2000, &obj, &MyObject::scheduleDeletion); // 2秒后安排删除对象
return a.exec(); // 进入事件循环
}
#include "main.moc"
在这个示例中,MyObject
类有一个槽 scheduleDeletion()
,它调用 deleteLater()
来安排对象的删除。在 main()
函数中,我们使用 QTimer::singleShot()
来安排在特定时间后调用这些槽。当应用程序进入事件循环时,它会在 2 秒后执行 scheduleDeletion()
,然后在事件循环的下一个空闲时间删除 obj
对象。