当前位置: 首页 > news >正文

Qt 中实现多线程的两种方式及结合

Qt 中实现多线程的两种方式及结合

一、两种方法

在 Qt 中实现多线程主要有两种方式:继承 QThread 类和基于 QObjectmoveToThread 方法。下面是这两种方法的对比和完整示例:

两种方法的对比

特性继承 QThreadmoveToThread
实现复杂度简单直接较复杂,需要信号槽机制配合
适用场景单一任务的线程复杂对象需要在线程中运行
生命周期管理线程结束时对象自动销毁需手动管理对象生命周期
事件循环默认没有事件循环默认有完整的事件循环
通信机制通过共享变量或手动信号槽天然支持信号槽跨线程安全通信
推荐指数⭐⭐⭐ (适合简单任务)⭐⭐⭐⭐⭐ (现代推荐)

下面是两种方法的完整实现示例:

方法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版本为例)

  1. 创建Qt控制台项目
  2. 添加main_movetothread.cpp文件
  3. 在项目文件(.pro)中添加:
QT += core 
CONFIG += c++11
  1. 编译运行

两种方法的核心区别图示

传统方法 (继承QThread)
┌─────────────┐      ┌─────────────┐
│   QThread   │      │   QThread   │
│ ┌─────────┐ │      │ ┌─────────┐ │
│ │ run()   │ │      │ │ run()   │ │
│ │ 直接访问 │ │      │ │ 直接执行  │ │
│ │ 成员变量 │ │      │ │ 任务逻辑  │ │
│ └─────────┘ │      │ └─────────┘ │
└─────────────┘      └─────────────┘现代方法 (moveToThread)
┌─────────────┐      ┌─────────────┐
│   QThread   │      │   QObject   │
│ 事件循环驱动  │      ├─────────────┤
└──────┬──────┘      │ 业务逻辑     ││ 信号槽通信    │ 封装在槽函数  │
└──────▼──────┐      └──────┬──────┘
│  工作线程	  │      │  工作对象	   │
│ 不包含具体	  │      │ 可灵活移动到  │
│ 业务实现	  │      │ 不同线程	   │
└─────────────┘      └─────────────┘

关键实践建议:

  1. 简单任务选择继承方式:当只需要简单并行执行任务时,继承 QThread 更直接
  2. 复杂应用选择moveToThread
    • 需要多个工作对象在同一线程中时
    • 需要与GUI主线程频繁交互时
    • 需要完整事件循环处理定时器、网络等时
  3. 同步工具
    • 使用 QMutex 保护共享资源
    • 使用 QWaitCondition 协调线程执行
    • 使用 QSemaphore 实现高级同步
  4. 避免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
线程执行完成
http://www.lryc.cn/news/611526.html

相关文章:

  • Pytest项目_day05(requests加入headers)
  • 8.6 JavaWeb(请求响应 P67-P74)
  • 部署Web UI自动化测试平台:SeleniumFlaskTester
  • UI测试平台TestComplete的AI视觉引擎技术解析
  • QT+opencv+yolov8推理
  • 移动端跨平台框架(支持Harmony、iOS、Android)
  • C语言:指针(1-2)
  • Kaggle 经典竞赛泰坦尼克号:超级无敌爆炸详细基础逐行讲解Pytorch实现代码,看完保证你也会!!!
  • 霍尔传感器
  • 碰撞问题的分析
  • 什么是CDN, 它为什么更快
  • 《算法导论》第 7 章 - 快速排序
  • 概率/期望 DP Jon and Orbs
  • 机器学习④【算法详解:从决策树到随机森林】
  • 一周学会Matplotlib3 Python 数据可视化-图形的组成部分
  • 场外期权的卖方是什么策略?
  • Python包管理新利器:uv全面解析与Conda对比指南
  • 从 LinkedIn 到 Apache:Kafka 的架构设计与应用场景
  • KafKa 项目 -- GitHub 学习
  • 【第6话:相机模型2】相机标定在自动驾驶中的作用、相机标定方法详解及代码说明
  • 在Word和WPS文字中如何输入汉字的偏旁部首
  • SELinux加固Linux安全2
  • docker安装FFmpeg
  • SmartMediaKit 模块化音视频框架实战指南:场景链路 + 能力矩阵全解析
  • Flink CDC如何保障数据的一致性?
  • 力扣经典算法篇-44-组合总和(回溯问题)
  • Ubuntu20.04 离线安装 FFmpeg 静态编译包
  • 【unity实战】用unity实现一个3D俯视角暗杀潜行恐怖类游戏,主要是实现视野范围可视化效果
  • X86-ubuntu22.04远程桌面只有1/4无法正常操作
  • 问题定位排查手记1 | 从Windows端快速检查连接状态