Qt线程的几种使用方法

目录

  • 引言
  • 使用方法
    • 重写QThread::run()
    • moveToThread
    • QRunnable使用
    • QtConcurrent使用
  • 完整代码

引言

多线程不应该是一个复杂而令人生畏的东西,它应该只是程序员的一个工具,不应该是调用者过多记忆相关概念,而应该是被调用方应该尽可能的简化调用,这也是Qt多线程接口演化的方向。

目前Qt的多线程调用提供了三种方式,一种是子类化QThread后重写run函数,一种是将对象移动到特定线程中,还有一种是通过重写QRunnable的run函数搭配线程池实现,最后一种则是调用高级接口Qt::Concurrent。这三种方式在后面章节会进行详细阐述。

调用方式的简化意味者调用者能够花更多的时间去思考线程的使用是否合理。在通常情况下,我们是将耗时操作推进子线程中执行,防止主线程被阻塞,但在推入子线程之前,应该考虑原有的耗时操作是否有优化控件,能不能将其时间复杂度降低。因为移动到子线程只是解决了界面卡顿的表面问题,需要消耗的资源依然被消耗,如果是无意义的消耗会造成对整个系统的拖累。

除了需要考虑优化函数执行逻辑,还需要考虑的是限制子线程个数,不能无限制推高单个应用的线程数。一个就是线程本事需要消耗资源,另一个是切换线程和保证线程数据安全的资源消耗也是很多的,而且Qt已经提供了很方便的线程池化方案,因此限制线程数是一个有必要且不难达成的良好习惯。
在这里插入图片描述

使用方法

重写QThread::run()

/*---------------------WorkerThread-------------------------*/
class WorkerThread : public QThread
{
    Q_OBJECT
public:
    explicit WorkerThread();
protected:
    void run();
signals:
    void resultReady(const QString &s);
};

void WorkerThread::run(){
    /* ... here is the expensive or blocking operation ... */
}

/*----------------------MainWindow--------------------------*/
void MainWindow::startWorkInAThread()
{
    WorkerThread *workerThread = new WorkerThread();
    // Release object in workerThread
    connect(workerThread, &WorkerThread::finished, workerThread, &QObject::deleteLater);
    workerThread->start();
}

以上是通过子类化QThread后重写run函数实现子线程的调用,其中只有run函数内属于子线程,也就是QThread只是一个线程管理类,而其实例本身并不是一个线程,其他的成员和run函数并不在同一线程。

需要注意的是,上述调用线程的方法已经过时,目前官方不推荐使用。

moveToThread

/*--------------------Worker-------------------------*/
class Worker : public QObject
{
    Q_OBJECT
public:
    Worker();
    ~Worker();

signals:
    void startWorking();

public slots:
    void toWork();
};

/*---------------------------Controller----------------------------*/
class Controller : public QObject
{
    Q_OBJECT

public:
    Controller();
    ~Controller();

    void toWorkDirect();

signals:
    void startWorking();

private:
    QThread worker_thread_;
    Worker *worker_;
};
Worker::Worker()
{
    qDebug() << "constructor" << thread()->currentThreadId();
}

Worker::~Worker()
{
    qDebug() << "destructor" << thread()->currentThreadId();
}

void Worker::toWork()
{
    // to do something
    qDebug() << "work function" << thread()->currentThreadId();
}

Controller::Controller() {
    worker_ = new Worker;
    worker_->moveToThread(&worker_thread_);
    connect(this, &Controller::startWorking, worker_, &Worker::toWork);
    connect(this, &Controller::startWorking, worker_, [=]{
        // lambda函数同样在子线程中
        worker_->toWork();
    });
    connect(&worker_thread_, &QThread::finished, worker_, &QObject::deleteLater);
    worker_thread_.start();
}

Controller::~Controller() {
    worker_thread_.quit();
    worker_thread_.wait();
}

void Controller::toWorkDirect()
{
    qDebug() << "work direct function";
    worker_->toWork();
}

测试代码

auto button = new QPushButton(this);
connect(button, &QPushButton::clicked, this, [=]{
    auto controller = new Controller();
    emit controller->startWorking();

    controller->toWorkDirect();

    QTimer::singleShot(1000, [=]{
        controller->deleteLater();
    });
});

以上是通过moveToThread将耗时的工作对象推入线程的方法,也是目前官方推荐的QThread使用方法。

在这里插入图片描述

上述测试代码中,仅为示例展示线程归属,并不代表实际业务。通过按钮创建controller,点击发送信号startWorking以及直接调用toWorkDirect函数,再通过定时器析构退出线程。通过上图控制台打印可以看到,构造和直接调用函数都是在主线程中,只有通过信号槽连接的函数才会在子线程中,这里可以看出Qt线程的调用方式其实通过事件循环实现的,详细可以参考之前章节。

QRunnable使用

class RunnableObject : public QObject, public QRunnable
{
    Q_OBJECT
public:
    explicit RunnableObject(QObject *parent = nullptr);
    ~RunnableObject();

signals:
    void workFinished(QString message);

protected:
    void run() override {

        // to work...

        QString temp = QString(__FUNCTION__) + " 0x" + QString::number((qint64)QThread::currentThreadId(), 16);
        Q_EMIT workFinished(temp);
    };
};


void InitRunnable(QObject *parent)
{
    auto runnable_object_ = new RunnableObject(parent);
    auto runnable_thread_pool_ = new QThreadPool(parent);
    runnable_thread_pool_->start(runnable_object_);
}

首先RunnableObject 继承QRunnable,重写run实现需要放在线程中的函数,再通过线程池的启动函数start(),即可完成在线程池中运行。需要注意的是,默认执行结束后会销毁原来的执行对象,如果希望执行后依然保持工作对象,需要通过函数setAutoDelete(false)去设置。

QtConcurrent使用

auto temp_worker = new Worker();
QtConcurrent::run(temp_worker, &Worker::toWork);

上述代码则是通过高级接口QtConcurrent::run去实现的代码,将temp_worker的toWork函数直接推入子线程中,这样更符合调用者的直觉,相比于前两种调用方式更加简化,也是笔者推荐的线程使用方式。

auto thread_pool = new QThreadPool(this);
thread_pool->setMaxThreadCount(4);

auto temp_worker = new Worker();
QFuture<void> temp_future = QtConcurrent::run(thread_pool, temp_worker, &Worker::toWork);

上述代码是QtConcurrent::run的完整使用方法,包含线程池以及QFuture。如前文所述,Qt的线程池化方案非常简便,设置最大线程数即可。QFuture主要搭配QFutureWatcher使用,用于监控函数的返回值,详细可参考官方文档。

完整代码

示例声明

#ifndef EXAMPLEWIDGET4_HPP
#define EXAMPLEWIDGET4_HPP

#include <QWidget>

class QThread;
class QThreadPool;
class QTextEdit;
class QPushButton;
class ThreadObject;
class WorkerObject;
class RunnableObject;
class ExampleWidget4 : public QWidget
{
    Q_OBJECT
public:
    explicit ExampleWidget4(QWidget *parent = nullptr);
    ~ExampleWidget4();

signals:

private:
    void InitThreadRun();
    void InitMoveToThread();
    void InitRunnable();
    void InitConcurrent();

private slots:
    void toWorkDirect();

private:
    QTextEdit *text_edit_;
    QPushButton *thread_btn_;
    QPushButton *move_thread_btn1_;
    QPushButton *move_thread_btn2_;
    QPushButton *move_thread_btn3_;
    QPushButton *runnable_btn1_;
    QPushButton *runnable_btn2_;
    QPushButton *concurrent_btn1_;
    QPushButton *concurrent_btn2_;

    // QThread::run
    ThreadObject *thread_object_;

    // moveToThread
    QThread *worker_thread_;
    WorkerObject *worker_object_;

    // QRunnable
    RunnableObject *runnable_object_;
    QThreadPool *runnable_thread_pool_;

    // QtConcurrent
    WorkerObject *concurrent_worker_;
    QThreadPool *concurrent_thread_pool_;
};

#endif // EXAMPLEWIDGET1_HPP

示例实现

#include "example_widget4.hpp"
#include "thread_object.hpp"
#include "worker_object.hpp"
#include "runnable_object.hpp"

#include <QLabel>
#include <QTextEdit>
#include <QPushButton>
#include <QVBoxLayout>
#include <QtConcurrent>

ExampleWidget4::ExampleWidget4(QWidget *parent)
    : QWidget{parent}
{
    auto thread_id = new QLabel(this);
    thread_id->setText(QString("Main Thread: ") + " 0x" + QString::number((qint64)QThread::currentThreadId(), 16));

    text_edit_ = new QTextEdit(this);
    text_edit_->setReadOnly(true);

    auto clear_btn = new QPushButton("clear", this);
    connect(clear_btn, &QPushButton::clicked, this, [=] {
        text_edit_->clear();
    });

    auto main_btn = new QPushButton("Main", this);
    connect(main_btn, &QPushButton::clicked, this, [=] {
        text_edit_->append(QString("Main Thread: ") + " 0x" + QString::number((qint64)QThread::currentThreadId(), 16));
    });

    InitThreadRun();
    InitMoveToThread();
    InitRunnable();
    InitConcurrent();

    // 布局
    auto lable_layout = new QHBoxLayout;
    lable_layout->addWidget(thread_id);
    lable_layout->addStretch();
    lable_layout->addWidget(clear_btn);

    auto move_thread_layout = new QHBoxLayout;
    move_thread_layout->addWidget(move_thread_btn1_);
    move_thread_layout->addWidget(move_thread_btn2_);
    move_thread_layout->addWidget(move_thread_btn3_);

    auto runnable_layout = new QHBoxLayout;
    runnable_layout->addWidget(runnable_btn1_);
    runnable_layout->addWidget(runnable_btn2_);

    auto concurrent_layout = new QHBoxLayout;
    concurrent_layout->addWidget(concurrent_btn1_);
    concurrent_layout->addWidget(concurrent_btn2_);

    auto button_layout = new QVBoxLayout;
    button_layout->addWidget(main_btn);
    button_layout->addWidget(thread_btn_);
    button_layout->addLayout(move_thread_layout);
    button_layout->addLayout(runnable_layout);
    button_layout->addLayout(concurrent_layout);

    auto main_layout = new QVBoxLayout(this);
    main_layout->addLayout(lable_layout);
    main_layout->addWidget(text_edit_);
    main_layout->addLayout(button_layout);
}

ExampleWidget4::~ExampleWidget4()
{
    thread_object_->quit();
    thread_object_->wait();

    worker_thread_->quit();
    worker_thread_->wait();

    runnable_thread_pool_->waitForDone();

    concurrent_thread_pool_->waitForDone();
}

void ExampleWidget4::InitThreadRun()
{
    // QThread重写run函数
    thread_object_ = new ThreadObject(this);
    connect(thread_object_, &ThreadObject::workFinished, this, [this](QString message) {
        text_edit_->append(message);
    });

    thread_btn_ = new QPushButton("Thread Object Run", this);
    connect(thread_btn_, &QPushButton::clicked, thread_object_, [this] {
        thread_object_->start();
    });
}

void ExampleWidget4::InitMoveToThread()
{
    // moveToThread
    worker_thread_ = new QThread(this);
    worker_object_ = new WorkerObject;// moveToThread的WorkerObject不能有父对象
    worker_object_->moveToThread(worker_thread_);
    connect(worker_object_, &WorkerObject::workFinished, this, [this](QString message) {
        text_edit_->append(message);
    });
    connect(worker_thread_, &QThread::finished, worker_object_, &QObject::deleteLater);
    worker_thread_->start();

    move_thread_btn1_ = new QPushButton("Move to Thread Method1", this);
    connect(move_thread_btn1_, &QPushButton::clicked, worker_object_, &WorkerObject::toWork);

    move_thread_btn2_ = new QPushButton("Move to Thread Method2", this);
    connect(move_thread_btn2_, &QPushButton::clicked, worker_object_, [this] {
        worker_object_->toWork();
    });

    move_thread_btn3_ = new QPushButton("Move to Thread Method3", this);
    connect(move_thread_btn3_, &QPushButton::clicked, this, &ExampleWidget4::toWorkDirect);
}

void ExampleWidget4::InitRunnable()
{
    runnable_object_ = new RunnableObject(this);
    runnable_object_->setAutoDelete(false);// autoDelete()为trun,QThreadPool取得的所有权并自动删除它
    connect(runnable_object_, &RunnableObject::workFinished, this, [this](QString message) {
        text_edit_->append(message);
    });

    runnable_thread_pool_ = new QThreadPool(this);
    runnable_thread_pool_->setMaxThreadCount(1);

    runnable_btn1_ = new QPushButton("Runnable1", this);
    connect(runnable_btn1_, &QPushButton::clicked, this, [this] {
        runnable_thread_pool_->start(runnable_object_);
    });

    runnable_btn2_ = new QPushButton("Runnable2", this);
    connect(runnable_btn2_, &QPushButton::clicked, this, [this] {
        auto temp_runnable = new RunnableObject;
        connect(temp_runnable, &RunnableObject::workFinished, this, [this](QString message) {
            text_edit_->append(message);
        });

        QThreadPool::globalInstance()->start(temp_runnable);
    });
}

void ExampleWidget4::InitConcurrent()
{
    // QtConcurrent
    concurrent_worker_ = new WorkerObject(this);
    connect(concurrent_worker_, &WorkerObject::workFinished, this, [this](QString message) {
        text_edit_->append(message);
    });

    concurrent_thread_pool_ = new QThreadPool(this);
    concurrent_thread_pool_->setMaxThreadCount(1);

    concurrent_btn1_ = new QPushButton("Concurrent1", this);
    connect(concurrent_btn1_, &QPushButton::clicked, this, [this] {
        QtConcurrent::run(concurrent_thread_pool_, concurrent_worker_, &WorkerObject::toWork);
    });

    concurrent_btn2_ = new QPushButton("Concurrent2", this);
    connect(concurrent_btn2_, &QPushButton::clicked, this, [this] {
        // 默认使用应用程序公共线程池QThreadPool::globalInstance()
        QtConcurrent::run(concurrent_worker_, &WorkerObject::toWork);
    });
}

void ExampleWidget4::toWorkDirect()
{
    worker_object_->toWork();
}


ThreadObject声明

#ifndef THREADOBJECT_HPP
#define THREADOBJECT_HPP

#include <QThread>

class ThreadObject : public QThread
{
    Q_OBJECT
public:
    explicit ThreadObject(QObject *parent = nullptr);

signals:
    void workFinished(QString message);

protected:
    void run() override;
};

#endif // THREADOBJECT_HPP

ThreadObject实现

#include "thread_object.hpp"

ThreadObject::ThreadObject(QObject *parent)
    : QThread{parent}
{

}

void ThreadObject::run()
{
    QString temp = QString(__FUNCTION__) + " 0x" + QString::number((qint64)currentThreadId(), 16);
    Q_EMIT workFinished(temp);
}

WorkerObject声明

#ifndef WORKEROBJECT_HPP
#define WORKEROBJECT_HPP

#include <QObject>
#include <QThread>

class WorkerObject : public QObject
{
    Q_OBJECT
public:
    explicit WorkerObject(QObject *parent = nullptr);

signals:
    void workFinished(QString message);

public slots:
    void toWork();
};

#endif // WORKEROBJECT_HPP

WorkerObject实现

#include "worker_object.hpp"

#include <QThread>

WorkerObject::WorkerObject(QObject *parent)
    : QObject{parent}
{

}

void WorkerObject::toWork()
{
    QString temp = QString(__FUNCTION__) + " 0x" + QString::number((qint64)QThread::currentThreadId(), 16);
    Q_EMIT workFinished(temp);
}

RunnableObject声明

#ifndef RUNNABLEOBJECT_HPP
#define RUNNABLEOBJECT_HPP

#include <QObject>
#include <QRunnable>

class RunnableObject : public QObject, public QRunnable
{
    Q_OBJECT
public:
    explicit RunnableObject(QObject *parent = nullptr);
    ~RunnableObject();

signals:
    void workFinished(QString message);

protected:
    void run() override;
};

#endif // RUNNABLEOBJECT_HPP

RunnableObject实现

#include "runnable_object.hpp"

#include <QThread>

RunnableObject::RunnableObject(QObject *parent)
    : QObject{parent}
{

}

RunnableObject::~RunnableObject()
{

}

void RunnableObject::run()
{
    QString temp = QString(__FUNCTION__) + " 0x" + QString::number((qint64)QThread::currentThreadId(), 16);
    Q_EMIT workFinished(temp);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/27396.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Linux教程——常见Linux发行版本有哪些?

新手往往会被 Linux 众多的发行版本搞得一头雾水&#xff0c;我们首先来解释一下这个问题。 从技术上来说&#xff0c;李纳斯•托瓦兹开发的 Linux 只是一个内核。内核指的是一个提供设备驱动、文件系统、进程管理、网络通信等功能的系统软件&#xff0c;内核并不是一套完整的…

网络安全从业人员2023年后真的会被AI取代吗?

随着ChatGPT的火爆&#xff0c;很多人开始担心网络安全从业人员会被AI取代。如果说网络安全挖洞的话&#xff0c;AI可能真的能取代。但是网络安全不仅仅只是挖洞&#xff0c;所以AI只是能缓解网络安全人员不足的情况&#xff0c;但是是不会取代人类的作用的。 就拿最近很火的C…

【线性代数】

求解线性方程组 右乘向量/矩阵 把左边的矩阵拆成一个个列向量&#xff0c;右边的向量表示对左边列向量组的线性组合。 [ c o l 1 c o l 2 c o l 3 ] [ 3 4 5 ] [ 3 c o l 1 4 c o l 2 5 c o l 3 ] \left[\begin{array}{c} col_{1} & col_{2} & col_{3} \end{array}\…

WPS表格处理

wps表格中公式出来的内容如何转为纯文本 选中公式算出的结果区域&#xff0c;复制&#xff0c;在原区域上右键&#xff0c;选择性粘贴为数值&#xff0c;就转成文本了&#xff0c;当然公式也就消除了。 wps表格如何设置整列公式&#xff1f; 1、先来看看下面这个例子需做出商…

Git、Github、Gitee的区别

⭐作者主页&#xff1a;逐梦苍穹 ⭐所属专栏&#xff1a;Git 目录 1、Git2、Gitee3、GitHub 什么是版本管理&#xff1f;   版本管理是管理各个不同的版本&#xff0c;出了问题可以及时回滚。 1、Git Git是一个分布式版本控制系统&#xff0c;用于跟踪和管理代码的变化。它是…

【Ubuntu系统内核更新与卸载】

【Ubuntu系统内核更新与卸载】 1. 前言2. 内核安装2.1 系统更新2.2 官网下载 3. 内核卸载3.1 需求分析3.2 卸载方法 1. 前言 我们在搭建环境时常常遇到内核版本不匹配的问题&#xff0c;需要我们安装新的内核版本&#xff1b;有时又会遇到在安装软件时报错boot空间已满无法安装…

2021年国赛高教杯数学建模B题乙醇偶合制备C4烯烃解题全过程文档及程序

2021年国赛高教杯数学建模 B题 乙醇偶合制备C4烯烃 原题再现 C4 烯烃广泛应用于化工产品及医药的生产&#xff0c;乙醇是生产制备 C4 烯烃的原料。在制备过程中&#xff0c;催化剂组合&#xff08;即&#xff1a;Co 负载量、Co/SiO2 和 HAP 装料比、乙醇浓度的组合&#xff0…

(六)CSharp-CSharp图解教程版-委托

一、委托概述 1、什么是委托 委托和类一样&#xff0c;是一种用户定义类型&#xff08;即是一种类&#xff0c;所以也是一个引用类型&#xff09;。在它们组成的结构方面区别是&#xff0c;类表示的是数据和方法的集合&#xff0c;而委托则持有一个或多个方法。 可以把 deleg…

HNU-操作系统OS-作业1(4-9章)

这份文件是OS_homework_1 by计科2102 wolf 202108010XXX 文档设置了目录,可以通过目录快速跳转至答案部分。 第四章 4.1用以下标志运行程序:./process-run.py -l 5:100,5:100。CPU 利用率(CPU 使用时间的百分比)应该是多少?为什么你知道这一点?利用 -c 标记查看你…

[230604] 听力TPO66汇总·上篇| C1 L1 C2|10:20~12:00

目录​​​​​​​ Science Fiction And Sci-fi-C1 错题分析 C1-3 细节双选题 C1 精听练习 做题笔记 Financial Advice-C2 全对 C2 精听练习 Sleep-L1 错题分析 L1-4 细节题 L1-5 细节双选题 L1 精听练习 做题笔记 词汇&#xff1a;http://t.csdn.cn/Zhuws 两篇对…

Linux进程、用户、权限命令

进程管理命令 进程和程序的区别 1 程序是静态概念&#xff0c;本身作为一种软件资源长期保存&#xff1b;而进程是程序的执行过程&#xff0c;它是动态概念&#xff0c;有一定的生命期&#xff0c;是动态产生和消亡的。 2 程序和进程无一一对应关系。一个进程在活动中可有顺序…

软件测试03:软件工程和软件生命周期

软件测试03&#xff1a;软件工程和软件生命周期 软件危机 软件危机是指落后的软件生产方式无法满足迅速增长的计算机软件需求&#xff0c;从而导致软件开发与维护过程中出现一系列严重问题的现象。 软件工程 基本软件危机对于计算机发展的阻碍&#xff0c;1968年&#xff0…

一分钟学一个 Linux 命令 - tar

前言 大家好&#xff0c;我是 god23bin。今天给大家带来的是 Linux 命令系列&#xff0c;每天只需一分钟&#xff0c;记住一个 Linux 命令不成问题。今天&#xff0c;我们要介绍的是一个常用且强大的命令&#xff1a;tar。 什么是 tar 命令&#xff1f; tar 是 tape archive…

SUSTechPOINTS三维点云标注工具使用

官方地址&#xff1a;SUSTechPOINTS 官方中文教程 相关文章&#xff1a; OpenPCDet安装、使用方式及自定义数据集训练 安装 git clone https://github.com/naurril/SUSTechPOINTS cd SUSTechPOINTS pip install -r requirement.txt wget https://github.com/naurril/SUSTec…

STL——string和vector容器

初识STL **STL的基本概念****vector容器存放内置数据类型****在vector容器中存放自定义数据类型****vector容器嵌套vector容器****string容器——构造函数****string容器——赋值操作****string容器——字符串拼接****string容器——字符串的查找和替换****string容器——字符串…

Midjourney竞品Leap免费试用; Google 刚刚发布10门独立AI课程

&#x1f989; AI新闻 &#x1f680; Midjourney竞品&#xff0c;免费试玩AI图片生成工具Leap&#xff0c;细节还需提升 摘要&#xff1a;Leap是一款免费试玩的AI图片生成工具&#xff0c;用户可以选择不同的生成模型和步长及数量。功能上尚需提高细节把握能力&#xff0c;但…

线段树算法(C++/C)

目录​​​​​​​ 一、线段树算法的概念 二、为什么需要线段树 三、线段树算法的实现 &#xff08;1&#xff09;建树 &#xff08;2&#xff09;查询 &#xff08;3&#xff09;修改 &#xff08;4&#xff09;综合代码&#xff0c;求区间和 &#xff08;5&#xff…

Python暑假自律打卡学习班,免费,速来(2)

小朋友们好&#xff0c;大朋友们好&#xff01; 我是猫妹&#xff0c;一名爱上Python编程的小学生。 和猫妹学Python&#xff0c;一起趣味学编程。 很快就放暑假了&#xff0c;还有20多天吧&#xff01; 猫妹对这个暑假相当期待啊&#xff0c; 想想今年的五一劳动节有多火爆…

Openharmony添加编译自己应用

介绍一下Openharmony如何在庞大的编译构建系统中&#xff0c;增添自己想编译的内容。不定期更新~&#x1f438; gn官方文档&#xff1a; https://gn.googlesource.com/gn//main/docs/quick_start.md https://gn.googlesource.com/gn//master/docs/reference.md openharmony官…

Redis 消息队列 Stream

tip&#xff1a;作为程序员一定学习编程之道&#xff0c;一定要对代码的编写有追求&#xff0c;不能实现就完事了。我们应该让自己写的代码更加优雅&#xff0c;即使这会费时费力。 &#x1f495;&#x1f495; 推荐&#xff1a;体系化学习Java&#xff08;Java面试专题&#…