Qt 中实现多线程的两种方式及结合
Qt 中实现多线程的两种方式及结合
一、两种方法
在 Qt 中实现多线程主要有两种方式:继承 QThread
类和基于 QObject
的 moveToThread
方法。下面是这两种方法的对比和完整示例:
两种方法的对比
特性 | 继承 QThread | moveToThread |
---|---|---|
实现复杂度 | 简单直接 | 较复杂,需要信号槽机制配合 |
适用场景 | 单一任务的线程 | 复杂对象需要在线程中运行 |
生命周期管理 | 线程结束时对象自动销毁 | 需手动管理对象生命周期 |
事件循环 | 默认没有事件循环 | 默认有完整的事件循环 |
通信机制 | 通过共享变量或手动信号槽 | 天然支持信号槽跨线程安全通信 |
推荐指数 | ⭐⭐⭐ (适合简单任务) | ⭐⭐⭐⭐⭐ (现代推荐) |
下面是两种方法的完整实现示例:
方法1:继承 QThread
// main_thread_inheritance.cpp
#include <QCoreApplication>
#include <QThread>
#include <QMutex>
#include <QDebug>// 方式1:继承QThread实现
class WorkerThread : public QThread
{Q_OBJECT
public:explicit WorkerThread(int id, QObject *parent = nullptr): QThread(parent), m_id(id), m_counter(0) {}signals:void progress(int threadId, int count);protected:void run() override {qDebug() << "线程" << m_id << "开始运行";while(m_counter < 5) {msleep(300 + (qint64(QRandomGenerator::global()->generate()) % 200)); // 模拟工作QMutexLocker locker(&m_mutex); // 使用互斥锁保护数据m_counter++;emit progress(m_id, m_counter); // 通知主线程qDebug() << "线程" << m_id << "计数:" << m_counter;}qDebug() << "线程" << m_id << "运行结束";}private:int m_id;int m_counter;QMutex m_mutex;
};int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);qDebug() << "===== [继承QThread方式] =====";WorkerThread thread1(1);WorkerThread thread2(2);QObject::connect(&thread1, &WorkerThread::progress, [](int id, int count){qDebug() << "[主线程收到] 线程" << id << "已执行:" << count << "次";});thread1.start();thread2.start();thread1.wait();thread2.wait();qDebug() << "===== 所有线程结束 =====";return a.exec();
}#include "main_thread_inheritance.moc"
方法2:moveToThread(推荐方法)
// main_movetothread.cpp
#include <QCoreApplication>
#include <QThread>
#include <QObject>
#include <QDebug>
#include <QMutex>
#include <QRandomGenerator>// 方式2:基于moveToThread实现
class Worker : public QObject
{Q_OBJECT
public:explicit Worker(int id) : m_id(id), m_counter(0) {}public slots:void startWork() {qDebug() << "线程" << m_id << "开始运行";while(m_counter < 5) {// 模拟工作QThread::msleep(300 + (qint64(QRandomGenerator::global()->generate()) % 200));QMutexLocker locker(&m_mutex);m_counter++;emit progress(m_id, m_counter); // 通知主线程qDebug() << "线程" << m_id << "计数:" << m_counter;}qDebug() << "线程" << m_id << "运行结束";emit finished();}signals:void progress(int threadId, int count);void finished();private:int m_id;int m_counter;QMutex m_mutex;
};int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);qDebug() << "===== [moveToThread方式] =====";// 创建两个工作线程QThread thread1;QThread thread2;// 创建工作对象Worker workerObj1(1);Worker workerObj2(2);// 移动工作对象到线程workerObj1.moveToThread(&thread1);workerObj2.moveToThread(&thread2);// 连接信号槽QObject::connect(&thread1, &QThread::started, &workerObj1, &Worker::startWork);QObject::connect(&thread2, &QThread::started, &workerObj2, &Worker::startWork);QObject::connect(&workerObj1, &Worker::finished, &thread1, &QThread::quit);QObject::connect(&workerObj2, &Worker::finished, &thread2, &QThread::quit);QObject::connect(&workerObj1, &Worker::progress, [](int id, int count){qDebug() << "[主线程收到] 线程" << id << "已执行:" << count << "次";});// 启动线程thread1.start();thread2.start();// 等待线程结束thread1.wait();thread2.wait();qDebug() << "===== 所有线程结束 =====";return a.exec();
}#include "main_movetothread.moc"
编译运行方法 (以moveToThread版本为例)
- 创建Qt控制台项目
- 添加
main_movetothread.cpp
文件 - 在项目文件(.pro)中添加:
QT += core
CONFIG += c++11
- 编译运行
两种方法的核心区别图示
传统方法 (继承QThread)
┌─────────────┐ ┌─────────────┐
│ QThread │ │ QThread │
│ ┌─────────┐ │ │ ┌─────────┐ │
│ │ run() │ │ │ │ run() │ │
│ │ 直接访问 │ │ │ │ 直接执行 │ │
│ │ 成员变量 │ │ │ │ 任务逻辑 │ │
│ └─────────┘ │ │ └─────────┘ │
└─────────────┘ └─────────────┘现代方法 (moveToThread)
┌─────────────┐ ┌─────────────┐
│ QThread │ │ QObject │
│ 事件循环驱动 │ ├─────────────┤
└──────┬──────┘ │ 业务逻辑 ││ 信号槽通信 │ 封装在槽函数 │
└──────▼──────┐ └──────┬──────┘
│ 工作线程 │ │ 工作对象 │
│ 不包含具体 │ │ 可灵活移动到 │
│ 业务实现 │ │ 不同线程 │
└─────────────┘ └─────────────┘
关键实践建议:
- 简单任务选择继承方式:当只需要简单并行执行任务时,继承
QThread
更直接 - 复杂应用选择moveToThread:
- 需要多个工作对象在同一线程中时
- 需要与GUI主线程频繁交互时
- 需要完整事件循环处理定时器、网络等时
- 同步工具:
- 使用
QMutex
保护共享资源 - 使用
QWaitCondition
协调线程执行 - 使用
QSemaphore
实现高级同步
- 使用
- 避免UI线程阻塞:
- 耗时操作(如I/O、计算密集型任务)必须在工作线程中执行
- 主线程只负责UI更新和事件调度
两种方法都能正常工作,但现代Qt开发更推荐使用 moveToThread
方法,因为它提供了更灵活的对象管理和更清晰的事件驱动架构。
核心概念理解
在讨论这个区别前,需要理解两个核心概念:
二、在继承QThread中添加事件循环
在Qt的多线程编程中,事件循环的理解至关重要。让我们深入解释继承QThread方法无事件循环和moveToThread有事件循环的区别。
解决方案:
class ThreadWithEventLoop : public QThread {
protected:void run() override {qDebug() << "Thread ID:" << QThread::currentThreadId();// 转移对象到本线程this->moveToThread(this);// 创建并启动事件循环exec(); // 启动事件循环qDebug() << "Event loop exited";}
};
完整可运行示例:
#include <QCoreApplication>
#include <QThread>
#include <QDebug>
#include <QTimer>class ThreadWithEventLoop : public QThread {
protected:void run() override {qDebug() << "Thread ID:" << QThread::currentThreadId();// 创建定时器证明事件循环工作正常QTimer timer;int tickCount = 0;connect(&timer, &QTimer::timeout, this, [&]{qDebug() << "心跳 -" << ++tickCount;if(tickCount >= 5) {qDebug() << "请求结束事件循环";quit(); // 请求退出事件循环}});// 关键部分:移动对象到当前线程this->moveToThread(this);timer.start(500); // 启动每500毫秒的定时器QThread::run(); // 运行事件循环(调用exec())}
};int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);qDebug() << "主线程ID:" << QThread::currentThreadId();ThreadWithEventLoop thread;thread.start();// 等待线程结束thread.wait();qDebug() << "线程执行完成";return a.exec();
}
输出结果证明:
主线程ID: 0x1bb4
Thread ID: 0x1a30
心跳 - 1
心跳 - 2
心跳 - 3
心跳 - 4
心跳 - 5
请求结束事件循环
Event loop exited
线程执行完成