QThread 类是 Qt 线程模块中的核心类之一,它是一个与操作系统本身的线程模型对应的、可跨平台的线程抽象类,允许开发者创建自己的多线程应用程序。QThread 类将一个线程封装成了对象,使得线程可以在不同的上下文(如主线程或子线程)中进行管理和控制,而无需直接使用传统的线程库函数并处理复杂的线程同步问题。
在使用 QThread 进行多线程编程时,通常需要继承该类并实现其 run() 函数以完成线程运行的任务。run() 函数是 QThread 类的纯虚函数,开发者需要在该函数中编写具体的线程代码,并确保该函数终止后线程的生命周期也随之结束。除此之外,QThread 类还提供了一系列其他函数,用于控制线程的状态和执行轨迹,如:
- start():启动线程,并自动调用 QThread 对象的 run() 函数。
- wait():等待线程的结束,并阻塞调用线程,直到目标线程执行完毕。
- terminate():强制终止线程,可能导致资源泄露和未定义行为,不推荐使用。
- quit() / exit():请求线程停止,并安全退出线程事件循环。
在使用 QThread 时需要注意一些常见的问题,如线程安全、同步互斥、内存管理等,这些问题也是多线程编程中常见的难点。因此,Qt 提供了一系列其他的线程类和工具类,如 QMutex、QReadWriteLock、QSemaphore、QWaitCondition 等,帮助开发者更方便地控制线程同步和互斥访问共享资源的问题。
QThread有两种使用方式,可根据实际需求选择不同的使用场景。
直接继承 QThread 类
该方式主要用于需要在新线程中执行独立的任务,该任务不涉及到其他对象的运行和控制。此时最好直接使用继承 QThread
类的方式来创建新线程。这种方式实现简单,易于理解。
#include <QThread>
#include <QDebug>
#include <QCoreApplication>
class MyThread : public QThread
{
public:
void run() override {
qDebug() << "Thread ID:" << QThread::currentThreadId();
// 在这里编写具体的线程任务代码
}
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qDebug() << "Main thread ID:" << QThread::currentThreadId();
MyThread thread;
thread.start(); // 启动线程
thread.wait(); // 等待线程执行完毕
return a.exec();
}
使用 moveToThread 函数移动对象
该方式主要用于需要将一个对象从主线程移到新线程中,并且在新线程中使用该对象与其他对象进行交互和控制的场合。这种方式适合于多线程编程中经常用到的需求,如网络通信、I/O 等阻塞操作。实现相对较为灵活,可以调用对象的槽函数来启动新线程并处理数据。
需要注意的是,如果有多个对象需要在同一个线程中运行,则必须保证它们之间的竞争条件,并且不能产生死锁等问题,必要时需要使用互斥量或其他线程同步机制以确保程序正确性。同时,为了避免内存泄漏,当不再需要某些线程或对象时,应及时删除相关资源。
#ifndef WORKER_H
#define WORKER_H
#include <QObject>
#include <QThread>
#include <QDebug>
class Worker : public QObject
{
Q_OBJECT
public:
Worker() {}
virtual ~Worker(){}
public slots:
void doWork() {
qDebug() << "Worker thread ID: " << QThread::currentThreadId();
}
};
#endif // WORKER_H
#include <QCoreApplication>
#include <QDebug>
#include <QObject>
#include <QThread>
#include "worker.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
// 创建一个新线程
QThread* thread = new QThread();
// 创建一个 Worker 对象并将其移动到新线程中
Worker* worker = new Worker();
worker->moveToThread(thread);
qDebug() << "Main thread ID:" << QThread::currentThreadId();
// 在新线程中启动 Worker 的 doWork() 槽函数
QObject::connect(thread, &QThread::started, worker, &Worker::doWork);
thread->start();
// 等待线程执行完毕
thread->wait();
return a.exec();
}
需要注意的是,如果在主线程中直接调用wrker的槽函数,则该槽函数仍然在主线程执行。
lambda表达式结合QThread
还有一些场景,使用 lambda 表达式来实现 QThread 类的多线程可以有效地减少代码复杂度和维护成本。
// 主线程中
QThread* thread = new QThread();
// 创建 QUdpSocket 套接字对象
QUdpSocket* socket = new QUdpSocket();
socket->moveToThread(thread);
// 绑定信号槽
QObject::connect(thread, &QThread::started, [&]{
socket->bind(QHostAddress::AnyIPv4, 12345);
QObject::connect(socket, &QUdpSocket::readyRead, [&]{
while (socket->hasPendingDatagrams()) {
QByteArray datagram;
datagram.resize(socket->pendingDatagramSize());
socket->readDatagram(datagram.data(), datagram.size());
// 在子线程中处理数据
}
});
});
thread->start();
在上述代码中,我们先在主线程中创建了 QUdpSocket 套接字对象,并使用 moveToThread() 方法将其移动到了新开启的子线程中。接着在在子线程中绑定 readyRead 信号,在 lambda 函数中循环调用 hasPendingDatagrams() 和 readDatagram() 函数以接收和处理数据。
需要注意的是:在将套接字对象移动到其他线程中时,我们必须要在目标线程中创建该对象,否则可能出现程序崩溃或者无法接收到数据的问题。同时也需要注意不要对一个已经绑定了信号槽的套接字对象进行移动操作,否则会导致连接失效。