QT开发---多线程编程

多线程编程基础概述

在 Qt 开发中,多线程编程是处理耗时操作(如网络请求、数据计算、文件 IO 等)的关键技术,能够避免单线程导致的 UI 界面卡顿,提升应用响应性。

Detailed Description

A QThread object manages one thread of control within the program. QThreads begin executing in run(). By default, run() starts the event loop by calling exec() and runs a Qt event loop inside the thread.

QThread对象管理程序中的一个控制线程。QThreads在run()中开始执行。默认情况下,run()通过调用exec()启动事件循环,并在线程内运行Qt事件循环

Qt 多线程核心类与基本概念

Qt 的多线程机制基于QThread类,配合信号槽(Signal/Slot)实现线程间通信,核心类包括:

  • QThread:线程管理类,代表一个操作系统线程。
  • QObject:Qt 对象模型基础,支持通过moveToThread()关联到特定线程。
  • 同步工具:QMutex(互斥锁)、QSemaphore(信号量)、QWaitCondition(等待条件)等,用于线程间数据同步。
QThread类

博主在这里只对QThread类中常用到的方法做出介绍,若要深入学习QThread类,可在QT文档中深入学习

cpp 复制代码
class QThread
{
Public:
    bool wait(QDeadlineTimer deadline = QDeadlineTimer(QDeadlineTimer::Forever))
    bool wait(unsigned long time)
    阻塞当前调用线程,等待目标线程结束(最多等待time毫秒)
    确保线程执行完毕后再进行后续操作(如释放资源)

Static Public:
    QThread *currentThread()
    返回当前执行的线程
    Qt::HANDLE currentThreadId()
    返回当前执行线程的系统级 ID(Qt::HANDLE,本质是操作系统线程 ID)
    
    void msleep(unsigned long msecs)
    (since 6.6) void sleep(std::chrono::nanoseconds nsecs)
    void sleep(unsigned long secs)
    void usleep(unsigned long usecs)
    使当前执行线程休眠指定时间(毫秒 / 秒 / 微秒)
    注意:
    若在主线程调用,会导致 UI 卡顿(禁止!)
    若在子线程调用,仅阻塞当前子线程,不影响其他线程


Protected:
    int exec()
    启动线程的事件循环(类似QApplication::exec()),使线程能处理信号槽和事件
    exec()会阻塞直到exit()/quit()被调用,线程结束后发出finished()信号

    virtual void run()
    线程入口函数,必须在子线程中执行(由start()触发)
    默认实现仅调用exec()(开启事件循环),若重写run()且未调用exec()
    则线程执行完run()后直接结束

Signals:
    void finished()
    线程结束后发出(run()执行完毕或exit()被调用),常用于清理资源

    void started()
    线程启动后(run()开始执行前)发出,可用于初始化操作

Public Slots:
    void exit(int returnCode = 0)
    退出事件循环,returnCode作为exec()的返回值(0 表示正常退出)

    void quit()
    等价于exit(0),常用于通知线程优雅退出

    exec()会阻塞直到exit()/quit()被调用,线程结束后发出finished()信号

    void start(QThread::Priority priority = InheritPriority)
    启动线程,操作系统会创建新线程并调用run()函数(线程入口)
    注意:
    不能重复调用start()(线程运行中再次调用会失败)
    priority参数用于设置线程优先级(如QThread::HighPriority),但受操作系统调度机制限制
    
}

多线程实现方式

继承QThread并重写run()(基础方式)

创建QThread子类,重写run()方法(线程入口),在run()中实现耗时逻辑

cpp 复制代码
#ifndef QTTHREAD_H
#define QTTHREAD_H

#include <QObject>
#include <QThread>
#include <QDebug>
class QTthread : public QThread
{
    Q_OBJECT
public:
    explicit QTthread(QObject *parent = nullptr);
    void run()override;
signals:
};
#endif // QTTHREAD_H


#include "qtthread.h"

QTthread::QTthread(QObject *parent)
    : QThread{parent}
{}
void QTthread::run()
{
    //获取当前所属线程
    qDebug() << QThread::currentThread() << "当前执行线程" << "子线程1!";
    for(int i = 0;i<10;i++)
    {
        qDebug() << i ;
        QThread::sleep(1);
    }
    QTthread::wait(5000);

}
cpp 复制代码
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    this->qtthread = new QTthread;
}

MainWindow w; 
//在主线程中启动其他子线程
 w.qtthread->start();

注意事项

  • run()是线程的入口,默认只执行一次;若需循环执行,可在run()中加while循环(配合退出标志)。
  • 子线程中禁止直接操作 UI 组件 (如QPushButtonQLabel),需通过信号槽通知主线程处理。
  • 线程结束后需调用wait()释放资源,避免内存泄漏。

QObject + moveToThread()(推荐方式)

moveToThread()Qt 中实现线程分离的核心函数 ,它可以将一个 QObject 及其子对象 "移动" 到指定的 QThread 线程中执行,从而实现任务逻辑与线程管理的解耦。更符合 Qt 的对象模型,灵活性更高。

moveToThread() 的核心作用
  • QObject 实例从当前所属线程 "迁移" 到目标 QThread 线程。
  • 迁移后,该对象的所有槽函数(以及通过信号触发的函数)会在目标线程中执行。
  • 实现了 "对象在哪条线程,其逻辑就在哪条线程执行" 的映射关系。

main.cpp

cpp 复制代码
#include "mainwindow.h"

#include <QApplication>
#include "worker.h"
#include <QObject>
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    // 获取当前所属线程
    qDebug() << w.thread() << "当前对象所属线程";

    // 创建子线程和工作对象
    QThread *thread1 = new QThread;
    Worker *worker1 = new Worker;

    QThread *thread2 = new QThread;
    Worker *worker2 = new Worker;

    //将工作对象移动到子线程
    worker1->moveToThread(thread1);
    worker2->moveToThread(thread2);
    //连接信号和槽
    //使用按键
    //QObject::connect(&w,&MainWindow::signal1,thread1,&QThread::started);
    QObject::connect(thread1, &QThread::started, worker1, &Worker::doWork);
    QObject::connect(worker1, &Worker::finished, thread1, &QThread::quit);
    //连接worker1 对象的 finished 信号到其自身的 deleteLater 槽函数。当 worker1 完成工作并发出 finished 信号时,worker1 将安排自己被删除。
    //deleteLater 是一个便利方法,它将对象的删除推迟到事件循环的下一个迭代,以避免在对象还在使用时就被删除。
    QObject::connect(worker1, &Worker::finished, worker1, &Worker::deleteLater);
    //连接thread1 线程的 finished 信号到其自身的 deleteLater 槽函数。
    //当 thread1 完成执行并发出 finished 信号时,thread1 将安排自己被删除。这确保了线程对象在不再需要时被适当地清理。
    QObject::connect(thread1, &QThread::finished, thread1, &QThread::deleteLater);

    QObject::connect(thread2, &QThread::started, worker2, &Worker::doWork);
    QObject::connect(worker2, &Worker::finished, thread2, &QThread::quit);
    QObject::connect(worker2, &Worker::finished, worker2, &Worker::deleteLater);
    QObject::connect(thread2, &QThread::finished, thread2, &QThread::deleteLater);

    // 启动线程
    thread1->start();
    thread2->start();

    //确保线程资源被正确释放
    thread1->quit();
    thread1->wait();
    thread2->quit();
    thread2->wait();

    return a.exec();
}

work.h

cpp 复制代码
#ifndef WORKER_H
#define WORKER_H

#include <QObject>
#include <QDebug>
#include <QThread>
class Worker : public QObject
{
    Q_OBJECT
public:
    explicit Worker(QObject *parent = nullptr);

public slots:
    void doWork();
signals:
    void finished();
};

#endif // WORKER_H

如果对象有父对象,则不能移动它

移动对象必须是QObject 及其子对象

与继承 QThread 方式的对比

特性 继承 QThread 重写 run() moveToThread() 方式
逻辑与线程耦合度 高(任务逻辑在 run() 中) 低(任务与线程分离)
多任务支持 需手动实现循环逻辑 天然支持(通过多个信号触发)
扩展性 差(线程管理与任务混合) 高(可动态添加任务对象)
事件循环支持 需显式调用 exec() 线程启动后自动进入事件循环

优势

  • 任务逻辑与线程管理分离,一个线程可处理多个任务(通过多个信号触发)。
  • 避免继承QThread带来的资源管理问题,更符合 Qt 的 "组合优于继承" 原则。

QThreadPool + QRunnable(线程池,适合短期任务)

对于大量短期任务(如并发网络请求),使用线程池可避免频繁创建 / 销毁线程的开销。QThreadPool管理线程复用,QRunnable封装任务逻辑。

线程池(QThreadPool)中的线程是在需要时由线程池动态创建的,其来源和生命周期由线程池自动管理,无需开发者手动干预。

线程的创建时机

当通过 QThreadPool::start() 提交任务(QRunnable)时:

  • 若线程池中存在空闲线程,则直接复用该线程执行新任务。
  • 没有空闲线程 ,且当前线程数未达到线程池的最大限制maxThreadCount),线程池会新建一个系统级线程 (通过操作系统 API 创建,如pthread_createCreateThread)。
  • 若已达最大线程数,新任务会进入等待队列,直到有线程空闲后再被调度执行。

QThreadPool manages and recycles individual QThread objects to help reduce thread creation costs in programs that use threads. Each Qt application has one global QThreadPool object, which can be accessed by calling globalInstance().

QThreadPool管理和回收单个QThread对象,以帮助减少使用线程的程序中的线程创建成本。每个Qt应用程序都有一个全局QThreadPool对象,可以通过调用globalInstance()来访问。

QThreadPool 默认会自动删除 QRunnable。如需修改这一行为,可调用 QRunnable::setAutoDelete() 来关闭或开启自动删除标志。

通过在当前 QRunnable::run() 内部调用 tryStart(this),QThreadPool 允许同一个 QRunnable 被多次执行。如果启用了 autoDelete,那么当最后一个线程退出 run() 函数时,该 QRunnable 就会被销毁。若在 autoDelete 已开启的情况下仍使用同一个 QRunnable 多次调用 start(),则会产生竞态条件,因此这种做法不推荐。

处于空闲状态超过一定时间的线程会被自动回收。默认的回收超时为 30 000 毫秒(30 秒),可通过 setExpiryTimeout() 进行调整;若设为负值,则禁用回收机制。

使用 maxThreadCount() 可查询当前允许的最大线程数,如有需要,可用 setMaxThreadCount() 进行修改。默认的最大线程数等于 QThread::idealThreadCount()。activeThreadCount() 返回当前正在执行任务的线程数量。

reserveThread() 可以为外部用途预留一条线程;用完后务必调用 releaseThread() 将其归还,以便线程池复用。这两个函数本质上会临时增减"活跃线程"计数,适用于实现 QThreadPool 不可见的耗时操作。

请注意,QThreadPool 是一个用于管理线程的低级类;如需更高级别的并发方案,可参考 Qt Concurrent 模块。
The QRunnable class is an interface for representing a task or piece of code that needs to be executed, represented by your reimplementation of the run() function.
You can use QThreadPool to execute your code in a separate thread. QThreadPool deletes the QRunnable automatically if autoDelete() returns true (the default). Use setAutoDelete() to change the auto-deletion flag.
QThreadPool supports executing the same QRunnable more than once by calling QThreadPool::tryStart(this) from within the run() function. If autoDelete is enabled the QRunnable will be deleted when the last thread exits the run function. Calling QThreadPool::start() multiple times with the same QRunnable when autoDelete is enabled creates a race condition and is not recommended.

QRunnable 是一个接口类,用于表示"需要被执行的任务或代码段",具体表现为你在派生类中重新实现的 run() 函数。

你可以把任务交给 QThreadPool,让它在独立的线程中运行。默认情况下,若 autoDelete() 返回 true(默认值),QThreadPool 会在任务完成后自动删除该 QRunnable 对象;如需关闭或重新开启这一行为,可调用 setAutoDelete() 进行设置。

QThreadPool 支持让同一个 QRunnable 被多次执行:只需在 run() 函数内部调用 QThreadPool::tryStart(this) 即可。若 autoDelete 已启用,则只有当最后一个线程退出 run() 函数时,该 QRunnable 才会被销毁。需要特别注意的是,如果 autoDelete 为 true,却又多次对同一个 QRunnable 调用 QThreadPool::start(),就会产生竞态条件,因此这种做法并不推荐。

特点

  • 适合轻量、短期任务,自动管理线程生命周期。
  • 通过setMaxThreadCount()控制并发数,避免系统资源耗尽。
cpp 复制代码
#include "mainwindow.h"

#include <QApplication>
#include "task.h"
#include <QThreadPool>
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    //创建多个线程
    Task* task = new Task[3];
    //将线程加入到线程池中
    for(int i = 0;i<3;i++)
    {
        QThreadPool::globalInstance()->start(task+i); //&task[i]
    }
    /*
    QVector<Task*> tasks;
    for (int i = 0; i < 3; ++i)
    {
        Task* t = new Task;
        tasks.append(t);
        QThreadPool::globalInstance()->start(t);
    }
    */
    w.show();
    return a.exec();
}
cpp 复制代码
#include "task.h"

Task::Task() {
    setAutoDelete(false);
}

void Task::run()
{
    //获取当前所属线程
    qDebug() << QThread::currentThread() << QThread::currentThreadId();
}

QtConcurrent(线程池)

QtConcurrent 是 Qt 提供的基于线程池的高级并发编程框架,它封装了线程管理的复杂细节,让开发者可以更简洁地实现并行任务处理,无需直接操作 QThread 或线程池底层接口。

The Qt Concurrent module provides high-level APIs that make it possible to write multi-threaded programs without using low-level threading primitives such as mutexes, read-write locks, wait conditions, or semaphores. Programs written with Qt Concurrent automatically adjust the number of threads used according to the number of processor cores available. This means that applications written today will continue to scale when deployed on multi-core systems in the future.

Qt Concurrent 模块提供了一套高级 API,使得编写多线程程序时无需直接使用诸如互斥锁、读写锁、等待条件或信号量等低级线程原语。使用 Qt Concurrent 编写的程序会根据可用的处理器核心数自动调整所用线程数量,这意味着今天开发的应用程序在未来部署到多核系统时,仍能自动扩展性能。

Qt Concurrent 提供函数式编程风格的并行列表处理 API,适用于共享内存(非分布式)系统的 MapReduce 与 FilterReduce 实现,并包含在 GUI 应用中管理异步计算的工具类:

并行 Map 与 Map-Reduce
• QtConcurrent::map():对容器中的每一项应用给定函数,就地修改元素。
• QtConcurrent::mapped():与 map() 类似,但返回一个包含修改结果的新容器。
• QtConcurrent::mappedReduced():在 mapped() 的基础上,将修改后的结果归约(折叠)为单一结果。

并行 Filter 与 Filter-Reduce
• QtConcurrent::filter():根据过滤函数的结果,从容器中移除所有不符合条件的元素。
• QtConcurrent::filtered():与 filter() 类似,但返回一个包含过滤结果的新容器。
• QtConcurrent::filteredReduced():在 filtered() 的基础上,将过滤后的结果归约为单一结果。

并行运行
• QtConcurrent::run():在另一线程中运行指定函数。

并发任务
• QtConcurrent::task():创建 QtConcurrent::QTaskBuilder 实例,可用来调整参数并在独立线程中启动任务。

异步结果与监控
• QFuture:表示一次异步计算的结果。
• QFutureIterator:可用于遍历 QFuture 提供的结果。
• QFutureWatcher:通过信号-槽机制监控 QFuture。
• QFutureSynchronizer:便利类,可自动同步多个 QFuture。
• QPromise:向 QFuture 报告异步计算的进度与结果;当 QFuture 请求时,可暂停或取消任务。

Qt Concurrent 支持多种 STL 兼容的容器和迭代器类型,但在使用具有随机访问迭代器的 Qt 容器(如 QList)时效果最佳。map 与 filter 系列函数既接受完整容器,也接受 begin/end 迭代器对。

核心优势
  • 自动线程管理:内部维护线程池,自动根据 CPU 核心数调整线程数量,避免手动创建 / 销毁线程的开销。
  • 简化并行代码 :通过高阶函数(如 runmapfilter)实现并行逻辑,代码更简洁。
  • 与 Qt 信号槽集成 :可通过 QFutureWatcher 监听任务状态,轻松获取结果或进度。
  • 支持多种并行模式:适合单任务并行、批量数据处理等场景。
任务状态跟踪(QFutureWatcher

QFutureWatcher 是连接 QtConcurrent 任务与主线程的桥梁,通过信号槽实现非阻塞的状态监听,避免使用 waitForFinished() 阻塞主线程。

常用信号

  • finished():任务完成时触发。
  • progressValueChanged(int):任务进度更新(需任务支持)。
  • canceled():任务被取消时触发。

非阻塞运行情况:

cpp 复制代码
void MainWindow::on_pushButton_clicked()
{
    //提交任务到线程池,返回QFuture对象(用于跟踪任务)
    QFuture <int> future = QtConcurrent::run(worktask,5);
    //阻塞当前线程,直到关联的并发任务执行完毕
    //future.waitForFinished();
    //非阻塞
    //创建QFutureWatcher,用于监听任务状态
    QFutureWatcher<int> *watcher = new QFutureWatcher<int>(this);

    //连接finished信号:任务完成时触发,非阻塞
    connect(watcher, &QFutureWatcher<int>::finished, this, [=]()
    {
        //安全获取结果(此时任务已完成)
        qDebug() << "任务结果:" << watcher->result();
        //释放watcher资源(避免内存泄漏)
        watcher->deleteLater();
    });

    //将watcher与future关联,开始监听
    watcher->setFuture(future);
    //qDebug() << future.result();
    //注意:此时当前线程不会阻塞,会继续执行后续代码
    qDebug() << "任务已提交,当前线程继续执行...";
    
}

任务已提交,当前线程继续执行...
任务结果: 120

阻塞运行情况:

cpp 复制代码
void MainWindow::on_pushButton_clicked()
{
    //提交任务到线程池,返回QFuture对象(用于跟踪任务)
    QFuture <int> future = QtConcurrent::run(worktask,5);
    //阻塞当前线程,直到关联的并发任务执行完毕
    future.waitForFinished();
    /*
    //非阻塞
    //创建QFutureWatcher,用于监听任务状态
    QFutureWatcher<int> *watcher = new QFutureWatcher<int>(this);

    //连接finished信号:任务完成时触发,非阻塞
    connect(watcher, &QFutureWatcher<int>::finished, this, [=]()
    {
        //安全获取结果(此时任务已完成)
        qDebug() << "任务结果:" << watcher->result();
        //释放watcher资源(避免内存泄漏)
        watcher->deleteLater();
    });

    //将watcher与future关联,开始监听
    watcher->setFuture(future);
    */
    qDebug() << future.result();
    //注意:此时当前线程不会阻塞,会继续执行后续代码
    qDebug() << "任务已提交,当前线程继续执行...";
    
}

120
任务已提交,当前线程继续执行...

结语:

无论你是初学者还是有经验的开发者,我希望我的博客能对你的学习之路有所帮助。如果你觉得这篇文章有用,不妨点击收藏,或者留下你的评论分享你的见解和经验,也欢迎你对我博客的内容提出建议和问题。每一次的点赞、评论、分享和关注都是对我的最大支持,也是对我持续分享和创作的动力

相关推荐
float_六七23 分钟前
JavaScript:现代Web开发的核心动力
开发语言·前端·javascript
一车小面包24 分钟前
Python高级入门Day6
开发语言·python
祁同伟.29 分钟前
【C++】类和对象(中)构造函数、析构函数
开发语言·c++
a cool fish(无名)1 小时前
rust-方法语法
开发语言·后端·rust
摸鱼仙人~1 小时前
HttpServletRequest深度解析:Java Web开发的核心组件
java·开发语言·前端
郝学胜-神的一滴2 小时前
C++ 类型萃取:深入理解与实践
开发语言·c++·程序人生
喵手2 小时前
Java 11 新特性:从模块化到 HTTP/2 深度解析
java·开发语言·http
程序员编程指南2 小时前
Qt 网络编程进阶:网络安全与加密
c语言·网络·c++·qt·web安全
2301_803554522 小时前
【无标题】
开发语言·qt
Python涛哥2 小时前
go语言基础教程:【2】基础语法:基本数据类型(整形和浮点型)
android·开发语言·golang