QT中多线程写法

news2025/4/16 10:54:05

转自个人博客:QT中多线程写法

1. QThread及moveToThread()

使用情况:

多使用于需要将有着复杂逻辑或需要一直占用并运行的放入子线程中执行的情况,moveToThread是将整个类的对象移入子线程。

优缺点:

  • 优点:更符合QT的逻辑设计;灵活使用管理线程;将类的任务逻辑与线程逻辑区分开。
  • 缺点:不易于单独对指定函数单独放入子线程,也就不易于将一个类中多个函数放入多个子线程执行。

使用方法:

创建目标类对象后为其创建一个对应的QThread对象,利用信号和槽的机制,将类的执行与线程的触发连接起来,就可以利用开启线程来开始任务的执行。

注意,moveToThread的对象可以是QObject等,但不能是GUI相关的,如QWidget等。

#include <QObject>

class worker : public QObject {
    Q_OBJECT
public:
    worker(...) {...}
    
public slots:
    void doWork();
};
#include <QObject>
#include <QThread>

class mainClass : public QObject {
    Q_OBJECT
public slots:
    mainClass(){
        m_worker = new worker();
        m_thread = new QThread();
        connect(m_thread, &QThread::started, m_worker, &worker::doWork);
    	m_worker->moveToThread(m_thread); // 将类对象移动到子线程中
        m_thread->start(); //通过线程的开启来执行任务
    }
    
private:
    worker *m_worker;
    QThread *m_thread;
    
};

2. 继承QThread类

2.1 QThread基础使用

使用情况:

  1. 用于自定义QThread类,扩充原始QThread的功能,使其更加满足自己的需求,本质上还是用moveToThread(),只是声明的QThread对象换成了自定义类的对象
  2. 用于直接将需要放入子线程执行的类继承QThread,通过重载run()方法来控制任务的执行等,run()后就直接在子线程中执行任务了。

优缺点:

  • 优点:因为是直接继承整个QThread类,就可以重写其中的函数来实现指定的效果,对线程的控制更为底层,进入子线程的方法更为简单。
  • 缺点:这样也就需要自己把握好对线程生命周期的管理,同时也会将线程逻辑与任务逻辑结合在一起了;且不易于利用信号和槽等机制与其他线程进行通信。

使用方法:

继承QThread,一定需要重载run()来控制任务的运行,只有通过run()运行的任务才是运行在子线程中的

#include <QThread>
#include <QObject>

class MyThread : public QThread {
    Q_OBJECT
public:
    MyThread(QObject* parent = nullptr) : QThread(parent), m_stop(false) {}

    void run() override { // 重载run的方法
        while(m_isRun) {
            doWoker();
        }
    }
    
    void stop() {
        m_isRun = false;
    }
    
private:
    void doWoker() {
        ....
    }
    
private:
    bool m_isRun = true; // 自定义控制任务的关闭,可以采用其他方法
};
#include <QObject>

class mainClass : public QObject {
    Q_OBJECT
public slots:
    mainClass(){
		m_thread = new MyThread();
		m_thread->start(); // 直接通过start()进行run
    }
    
private:
    MyThread *m_thread;
    
};

2.2 QThread类相关方法

  1. run()

    virtual void run();

    程序的入口,自定义多线程时,继承后必须要重载的。通常将任务逻辑写在其中,使用run()运行任务逻辑才能在子线程中运行任务。

  2. *start()

    void start(QThread::Priority priority = QThread::InheritPriority);

    用于启动线程,线程会从run()方法开始执行任务,可以通过设置参数指定线程的优先级,优先级依次递增为:

    • QThread::IdlePriority:最小的优先级
    • QThread::LowestPriority
    • QThread::LowPriority
    • QThread::NormalPriority
    • QThread::HighPriority
    • QThread::HighestPriority
    • QThread::TimeCriticalPriority
    • QThread::InheritPriority:继承父线程的优先级
  3. exec()

    int exec();

    启动线程的事件循环。对于需要在子线程中处理信号和槽的情况,通常需要调用 exec() 来让线程进入事件循环。

  4. exit()

    void exit(int returnCode = 0);

    用于退出线程的事件循环,并将给定的返回码传递给 exec()。这不会立即终止线程,只是结束事件循环。

  5. quit()

    void quit();

    用于安全退出线程的事件循环,与 exit() 类似。它会在事件循环下一次检查事件时退出,并安全地结束线程。

  6. wait()

    bool wait(unsigned long time = ULONG_MAX);

    用于阻塞调用的线程,直到目标线程结束。可以通过设置时间参数来设定最多阻塞的毫秒数。通常用于等待线程的自然结束,确保所有任务执行完毕。

  7. terminate()

    void terminate();

    强制终止线程的执行。这是一个不安全的方法,不会释放线程正在使用的资源,因此尽量避免使用。推荐使用 quit()wait() 的方式来结束线程。

3. QtConcurrent和QFuture

3.1 QtConcurrent基础使用

使用情况:

多使用于需要将单一可调用对象(如函数等)放入子线程执行,可以更便捷地完成简单的并行任务;当对于一个类中,需要对多个方法放入子线程执行,使用这个方法也可以比其他方法更便捷地实现。

优缺点:

  • 优点:使用方法更简便;因为是直接对函数这种对象操作的,对应用场景的要求也就不高,更适用于轻量级并发任务;并且其可以自行管理线程,不需要手动释放等操作;也提供了较多方法可自定义。
  • 缺点:缺少更精细的底层多线程逻辑控制;不适合复杂的任务情景,不易用信号和槽机制。

使用方法:

使用QtConcurrent::run()方法直接将指定函数等对象移动到子线程并执行,调用参数和不调用参数的语法可参考下列示例,当然也支持使用Lambda表达式简洁化函数。

QtConcurrent返回QFuture对象以管理和监控异步任务执行结果,通过QFuture可以获取异步任务执行的结果或状态,绑定一个QFuture对象后可以在主线程任意获取此线程的相关状态。

#include <QObject>

class mainClass : public QObject {
    Q_OBJECT
public slots:
    mainClass(){
        // 1.无需调参
		QtConcurrent::run(this, &mainClass::doWork1); 
        
        // 2.调用参数,并绑定QFuture对象,获取执行结果
        int workValue = 10;
        QFuture<int> futureWork2 = QtConcurrent::run(this, &mainClass::doWork2, workValue); 
        futureWork2.waitForFinished();
        qDebug() << futureWork2.result();
        
        // 3.使用正则Lambda表达式代替目标参数
    }
    
    void doWork1(){...};
    int doWork2(int value){...};
    
private:
    MyThread *m_thread;
    
};

3.2 QtConcurrent相关方法

  1. QtConcurrent::run()

    参考3.1,执行单一异步任务。

  2. QtConcurrent::map()

    QFuture<void> map(Sequence<T> &sequence, functor mapFunc)

    对容器sequence中每一个元素都执行mapFunc的并行操作,并且不需要返回结果。sequence是类似于QList之类的容器对象,mapFunc是参数与容器类型Sequence<T> 的T相同的函数。

    void square(int &n) { // 不返回,直接修改参数
        n = n * n;
    }
    
    int main() {
        QList<int> list = {1, 2, 3, 4, 5};
    
        QtConcurrent::map(list, square);
    	for (int i : list) qDebug() << i;
    
        return 0;
    }
    
  3. QtConcurrent::mapped()

    QFuture<Sequence<T>> map(Sequence<T> &sequence, functor mapFunc)

    与QtConcurrent::map()类似,但其可以有返回值,返回一个包含各返回值的新容器。

    int square(int n) { // 返回结果
        return n * n;
    }
    
    int main() {
        QList<int> list = {1, 2, 3, 4, 5};
    
        QFuture<QList<int>> future = QtConcurrent::mapped(list, square);
        future.waitForFinished();
        
        QList<int> result = future.result(); // 获取任务运行结果
        for (int i : result) qDebug() << i;
    
        return 0;
    }
    
  4. QtConcurrent::mappedReduced()

    QFuture<Sequence<T>> mappedReduced(Sequence<T> &sequence, functor mapFunc, functor reduceFunc);

    与QtConcurrent::mapped()功能类似,在QtConcurrent::mapped()基础上增加了一个reduceFunc函数,这个方法不再返回返回值构成的新容器,而是利用reduceFunc函数将获取到的返回值进行处理。

    int square(int n) {
        return n * n;
    }
    
    void sum(int &result, const int &value) {
        result += value;
    }
    
    int main() {
        QList<int> list = {1, 2, 3, 4, 5};
        QFuture<int> future = QtConcurrent::mappedReduced(list, square, sum);
        future.waitForFinished();
        qDebug() << "Sum of squares:" << future.result(); // sum()将各子任务的square()返回值统一处理
        return 0;
    }
    
  5. QtConcurrent::filter()

    QFuture<void> filter(Sequence<T> &sequence, functor filterFunc);

    可以参考map()去理解,它遍历sequence容器,应用过滤器filterFunc函数,只保留那些满足条件的元素。注意,其通过返回值来进行过滤。

    bool isEven(const int &n) {
        return n % 2 == 0;
    }
    
    int main() {
        QList<int> list = {1, 2, 3, 4, 5, 6};
        QtConcurrent::filter(list, isEven);
        for (int i : list) qDebug() << i; // 输出 2 4 6
        return 0;
    }
    
    
  6. QtConcurrent::filterped()

    QFuture<Sequence<T>> filterped(Sequence<T> &sequence, functor filterFunc);

    参考QtConcurrent::mapped()去理解,即允许由过滤后的返回值构成的新容器。

  7. QtConcurrent::filterpedReduced()

    QFuture<Sequence<T>> filterpedReduced(Sequence<T> &sequence, functor mapFunc, functor reduceFunc);

    参考QtConcurrent::mappedReduced()去理解,即对过滤后的返回值使用reduceFunc函数进行处理。

3.3 QFuture详解

  1. 获取结果方法:result()返回异步任务结果,也即函数的返回值。该函数会判断结果是否为可用的,如果不可用则阻塞等待。如果可用则直接把结果返回。
  2. 设置运行状态方法:cancel()取消任务执行,pause()暂停任务执行,resumes()恢复任务执行。不是所有任务都支持设置运行状态
  3. 获取运行状态方法: isCanceled()检查任务是否被取消,isStarted()检查任务是否开始,isFinished()检查任务是否完成,isRunning()检查任务是否正在运行,isPaused()检查任务是否被暂停。
  4. 获取任务进度信息方法:progressValue()返回任务当前进度,progressMinimum()返回任务进度的最大值,progressMaximum()返回任务进度的最小值。
  5. 其他:上述有些方法在run的时候不一定有用,就可以使用waitForFinished(),阻塞等待直至任务完成时或方法可用时。

4. 附C++方法std::thread的简单使用

C++自带的多线程方法也很简便,但因为目前我使用的不多,故我在这里只是简单介绍一下。

std::thread 在构造时会立即启动线程,在构造时就可以传递一个函数或一个可调用对象(如函数对象或 Lambda表达式)作为线程中需要执行的任务。

在构造完后,可以用以下两种方法选择线程运行模式:

  • join()
    等待线程完成并阻塞当前线程。如果你不 join 一个线程,在程序结束时,未 join 的线程可能会导致程序异常终止。
  • detach()
    将线程与主线程分离,允许它在后台独立执行。使用 detach() 后,主线程不再等待该线程。
#include <iostream>
#include <thread>

void square1(int n) { // 传参
    std::cout << "the square of number : " << n;
}

void square2(int &n) { // 引用传参
    n = n * n;
    std::cout << "the square of number : " << n;
}

int main() {
    int value = 2;
    
    std::thread t1(square1, value);  // 传参
    t1.join(); // 使用join等待线程结束
    
    std::thread t2(square2, std::ref(value));  // 通过std::ref传递引用
    t2.detach(); // 使用detach分离线程,后台运行
    return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【leetcode hot 100 72】编辑距离

解法一&#xff1a;递归 解法二&#xff1a;&#xff08;动态规划&#xff09;①定义&#xff1a;dp[i][j]为word1中前i个字符转化为word2中前j个字符所需操作数;dp[m1][n1] ②初始状态&#xff1a;dp[0][j]j(0变为j&#xff0c;需要j步)&#xff0c;dp[i][0]i(i变为0&#xff…

Java练习——day1(反射)

文章目录 练习1练习2练习3思考封装原则与反射合理使用反射“破坏”封装的场景 练习1 编写代码&#xff0c;通过反射获取String类的所有公共方法名称&#xff0c;并按字母顺序打印。 示例代码&#xff1a; import java.lang.reflect.Method; import java.util.Arrays;public …

Docker 安装 Elasticsearch 8.x

Docker 安装 Elasticsearch 8.x 前言一、准备工作二、设置容器的目录结构三、启动一个临时的容器来复制配置文件四、复制配置文件到本地目录五、删除临时容器六、创建并运行容器&#xff0c;挂载本地目录七、修改文件配置监听端口八、端口配置&#xff1a;Host 网络模式 vs Por…

Vue工程化开发脚手架Vue CLI

开发Vue有两种方式 核心包传统开发模式&#xff1a;基于html / css / js 文件&#xff0c;直接引入核心包&#xff0c;开发 Vue。工程化开发模式&#xff1a;基于构建工具&#xff08;例如&#xff1a;webpack&#xff09;的环境中开发Vue。 脚手架Vue CLI Vue CLl 是 Vue 官方…

开源智慧巡检——无人机油田AI视频监控的未来之力

油田巡检&#xff0c;关乎能源命脉&#xff0c;却常受困于广袤地形、高危环境和人工效率瓶颈。管道泄漏、设备故障、非法闯入——这些隐患稍有疏忽&#xff0c;便可能酿成大患。传统巡检已无法满足现代油田对安全与效率的需求&#xff0c;而无人机油田巡检系统正以智能化之力重…

Django从零搭建卖家中心登陆与注册实战

在电商系统开发中&#xff0c;卖家中心是一个重要的组成部分&#xff0c;而用户注册与登陆则是卖家中心的第一步。本文将详细介绍如何使用Django框架从零开始搭建一个功能完善的卖家注册页面&#xff0c;包括前端界面设计和后端逻辑实现。 一、项目概述 我们将创建一个名为sel…

MySQL表的使用(4)

首先回顾一下之前所学的增删查改&#xff0c;这些覆盖了平时使用的80% 我们上节课中学习到了MySQL的约束 其中Primary key 是主键约束&#xff0c;我们今天要学习的是外键约束 插入一个表 外键约束 父表 子表 这条记录中classid为5时候&#xff0c;不能插入&#xff1b; 删除…

ollama修改配置使用多GPU,使用EvalScope进行模型压力测试,查看使用负载均衡前后的性能区别

文章目录 省流结论机器配置不同量化模型占用显存1. 创建虚拟环境2. 创建测试jsonl文件3. 新建测试脚本3. 默认加载方式&#xff0c;单卡运行模型3.1 7b模型输出213 tok/s3.1 32b模型输出81 tok/s3.1 70b模型输出43tok/s 4. 使用负载均衡&#xff0c;多卡运行4.1 7b模型输出217t…

Dijkstra算法求解最短路径—— 从零开始的图论讲解(2)

前言 在本系列第一期:从零开始的图论讲解(1)——图的概念,图的存储,图的遍历与图的拓扑排序-CSDN博客 笔者给大家介绍了 图的概念,如何存图,如何简单遍历图,已经什么是图的拓扑排序 按照之前的学习规划&#xff0c;今天笔者将继续带大家深入了解图论中的一个核心问题&#x…

[连载]Transformer架构详解

Transformer: Attention Is All You Need Paper 地址&#xff1a;https://arxiv.org/abs/1706.03762 Paper 代码&#xff1a;https://github.com/tensorflow/tensor2tensor Paper 作者&#xff1a;Ashish Vaswani,Noam Shazeer,Niki Parmar,Jakob Uszkoreit,Llion Jones,Aidan…

LVGL Video控件和Radiobtn控件详解

LVGL Video控件和Radiobtn控件详解 一、 Video控件详解1. 概述2. 创建和初始化3. 基本属性设置4. 视频控制5. 回调函数6. 高级功能7. 注意事项 二、Radiobtn控件详解1. 概述2. 创建和初始化3. 属性设置4. 状态控制5. 组管理6. 事件处理7. 样式设置8. 注意事项 三、效果展示四、…

组合数哭唧唧

前言&#xff1a;手写一个简单的组合数&#xff0c;但是由于长期没写&#xff0c;导致一些细节没处理好 题目链接 #include<bits/stdc.h> using namespace std; #define endl "\n"#define int long longconst int N (int)2e510; const int Mod (int)1e97;int…

NLP高频面试题(四十二)——RAG系统评估:方法、指标与实践指南

1. 引言:RAG系统概述与评估挑战 检索增强生成(Retrieval-Augmented Generation,简称 RAG)是近年来自然语言处理领域的一个重要进展。RAG系统在大型语言模型生成文本的过程中引入了外部检索模块,从外部知识库获取相关信息,以缓解纯生成模型可能出现的幻觉和知识盲点。通过…

Linux路漫漫

目录 Vim模式 基本操作 文本编辑 更多功能 1. 直接启动 Vim 2. 打开一个已存在的文件 3. 打开多个文件 4. 以只读模式打开文件 5. 从指定行号开始编辑 6. 快速打开并执行命令 7. 检查是否安装了 Vim 8. 退出 Vim 前提条件 SCP 命令格式 具体操作 1. Windows 命…

游戏引擎学习第227天

今天的计划 今天的工作重点是进行吸引模式&#xff08;attract mode&#xff09;的开发&#xff0c;主要是处理游戏的进出和其他一些小的细节问题&#xff0c;这些是之前想要整理和清理的部分。我做了一些工作&#xff0c;将游戏代码中的不同部分分离到逻辑上独立的区域&#…

一键直达:用n8n打造谷歌邮箱到Telegram的实时通知流

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 &#x1f38f;&#xff1a;你只管努力&#xff0c;剩下的交给时间 &#x1f3e0; &#xff1a;小破站 一键直达&#xff1a;用n8n打造谷歌邮箱到Telegram的实时通知流 前言n8n的强大之处实现简便性实战…

【QT】 QT定时器的使用

QT定时器的使用 1. QTimer介绍&#xff08;1&#xff09;QTimer的使用方法步骤示例代码1&#xff1a;定时器的启动和关闭现象&#xff1a;示例代码2&#xff1a;定时器每隔1s在标签上切换图片现象&#xff1a; (2)实际开发的作用 2.日期 QDate(1)主要方法 3.时间 QTime(1)主要方…

【自动化测试】如何获取cookie,跳过登录的简单操作

前言 &#x1f31f;&#x1f31f;本期讲解关于自动化测试函数相关知识介绍~~~ &#x1f308;感兴趣的小伙伴看一看小编主页&#xff1a;GGBondlctrl-CSDN博客 &#x1f525; 你的点赞就是小编不断更新的最大动力 &#x1f386;那么废话…

每天五分钟深度学习PyTorch:RNN CELL模型原理以及搭建

本文重点 RNN Cell(循环神经网络单元)是循环神经网络(RNN)的核心组成部分,用于处理序列数据中的每个时间步,并维护隐藏状态以捕获序列中的时间依赖关系。 RNN CELL的结构 RNN是一个循环结构,它可以看作是RNN CELL的循环,RNN CELL的结构如下图所示,RNN CELL不断进行…

【基于开源insightface的人脸检测,人脸识别初步测试】

简介 InsightFace是一个基于深度学习的开源人脸识别项目,由蚂蚁金服的深度学习团队开发。该项目提供了人脸检测、人脸特征提取、人脸识别等功能,支持多种操作系统和深度学习框架。本文将详细介绍如何在Ubuntu系统上安装和实战InsightFace项目。 目前github有非常多的人脸识…