QThread和std::thread

        在 Qt 中, 我们经常会用到多线程,这时候就需要纠结是使用 Qt 的 QThread 还是使用 C++ 标准库的 std::thread。

        这里记录一下我自己的理解,先介绍一下 QThread 和 std::thread 的使用方法,对比一下他们的不同,最后说一下我理解的应该怎么选择这两种方法。


QThread使用方法

        QThread 的使用有两种方法,一种是继承 QThread,然后重写 run() 函数,这种方法适合执行单个任务,比如计算某个耗时操作,执行完即可,不需要线程常驻。在 Qt5 之后 Qt 官方已经不推荐这种使用方法了,所以在这里也不赘述了。

        在 Qt5 之后,推荐的方法是创建一个工作类继承于 QObject,将工作对象通过 movetothread() 函数移动到一个新的线程中,这种方法更符合 Qt 的对象模型,并且更易于管理线程的生命周期和资源。下面是一个示例:

class Worker : public QObject {Q_OBJECT
public:Worker();~Worker();void doWorkA() {qDebug() << "Worker thread ID:" << QThread::currentThreadId();emit sigReadData("doWorkA");}void doWorkB() {qDebug() << "Worker thread ID:" << QThread::currentThreadId();emit sigResultReady();}signals:void sigReadData(QString data);void sigResultReady();
};
QThread *thread = new QThread();
Worker *worker = new Worker();worker->moveToThread(thread);// 连接信号槽
QObject::connect(worker, &Worker::sigReadData, this, &::);
QObject::connect(worker, &Worker::sigResultReady, thread, &QThread::quit);
QObject::connect(worker, &Worker::sigResultReady, worker, &QObject::deleteLater);
QObject::connect(thread, &QThread::finished, thread, &QObject::deleteLater); // 线程完成后自动删除qDebug() << "Main thread ID:" << QThread::currentThreadId();thread->start();
worker->doWorkA();
worker->doWorkB();

        这个 Worker 类没有做任何特别的事情,但它包含所有必需的元素。在这个例子中, doWorkA() 被调用时可以做一些你自己的处理,处理完后发送 sigReadData() 给主线程,doWorkB() 完成后它会发出信号 sigResultReady(),然后该信号将连接到 QThread 的槽 quit(),触发 QThread 的事件循环退出。事件循环退出后,QThread::finished() 信号被触发,连接到槽 QObject::deleteLater(),自动删除 QThread 对象。

        顺便说一句,这里要注意的非常重要的一件事是你永远不应该在 QObject 类的构造函数中分配堆对象(使用 new)。如果在构造函数中new,会报错 QObject:Cannot create children for a parent that is in a different thread。这个报错是由于这个 new 分配是在主线程,而不是新的子线程 QThread 实例上的。这意味着新创建的对象是由主线程拥有的,而不是 QThread 实例。所以,应该在 Worker 类的函数或者槽函数中分配此类资源,例如在 doWorkA() 或者doWorkB() 中使用new。在这种情况下,当调用该对象时,该对象将位于新线程实例上,因此新的线程实例将拥有该资源。


std::thread使用方法

        std::thread 是 C++11 引入的标准线程库,用于创建和管理线程。它提供了一种轻量级的方法来实现多线程编程,适合需要高性能和低延迟的应用。以下是 std::thread 的基本使用方法,可以使用下面几种方法创建线程:

#include <iostream>
#include <thread>class MyClass {
public:void memberFunction() {std::cout << "Hello from member function!" << std::endl;}
};void threadFunction() {std::cout << "Thread ID: " << std::this_thread::get_id() << std::endl;
}int main() {// 使用函数指针std::thread t1(threadFunction);std::cout << "Main thread ID: " << std::this_thread::get_id() << std::endl;if (t1.joinable()) {t1.join();    // 等待线程完成}// 使用成员函数MyClass obj;std::thread t2(&MyClass::memberFunction, &obj); // 传递对象指针if (t2.joinable()) {t2.join();    // 等待线程完成}// 使用lambda表达式std::thread t3([](){std::cout << "Hello from thread!" << std::endl;});if (t3.joinable()) {t3.join();    // 等待线程完成}return 0;
}

        管理线程有两个函数,一个是上面已经使用过的 join(),另一个是 detach():

std::thread t(threadFunction);
t.join(); // 阻塞主线程,直到 t 线程完成
std::thread t(threadFunction);
t.detach(); // 将线程与当前线程分离,允许 t 独立运行
// 注意:必须确保分离的线程在程序退出前完成


QThread和std::thread的选择

        在我看来,QThread 不仅仅是一个简单的线程类,它更像一个线程管理器。提供了与 Qt 框架的无缝集成,特别是与信号槽机制的结合使用,使得多线程编程更加方便和高效。我们可以把部分功能封装在一个工作对象中,然后把这个对象 movetothread,这样通过这个对象调用封装的所有方法,都是在子线程中进行,我只需要通过Qt的信号槽把子线程中我需要的数据传出来接收就可以在主线程或者UI上显示,而不会卡住主线程或者UI。

        例如,我要控制一个打印机或者串口,需要连接设备、发送消息和一直接收消息等功能,我们把这几个功能封装在一个打印机类或者串口类中。创建对象,然后通过 movetothread() 把这个类放到子线程,这样我们就可以把连接、发送接收处理消息这些功能都放到子线程中,而不影响主线程,接收到的消息需要传给主线程并显示的话就发送信号给主线程。

        而对于一些简单的多线程任务,例如并行计算、独立的后台任务等,使用 std::thread 可以更加轻便、直接和高效。std::thread 相对轻量,没有额外的框架依赖,适用于需要高性能和低开销的多线程操作。并且,std::thread 是 C++ 标准的一部分,保证了跨平台的一致性。如果需要在不同的平台上开发和运行代码,std::thread 提供了一种标准化的方法。

        总结一下:

  • 选择 QThread:如果你需要与 Qt 对象和事件循环进行集成。
  • 选择 std::thread:如果你需要一个更通用、更轻量级的多线程解决方案。

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

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

相关文章

【Redis】集群

文章目录 一、集群是什么&#xff1f;二、 Redis集群分布式存储为什么redis集群的最大槽数是16384&#xff08;不太懂&#xff09;redis的集群主节点数量基本不可能超过1000个 三、 配置集群&#xff08;三主三从&#xff09;3.1 配置config文件3.2 启动六台redis3.2 通过redis…

理兔chat开发日记

1.注册 注册跟以前的差不多&#xff0c;我们将我们的验证码放在redis下&#xff0c;我们在注册的时候先判断我们输入的验证码是否正确 验证码成功后在我们的实现类中&#xff0c;我们先判断邮箱是否重复&#xff0c;不重复我们就继续注册 我们拥有联号注册的功能&#xff0c;就…

unity局部坐标和世界坐标角度介绍

在Unity中&#xff0c;局部坐标&#xff08;Local Coordinates&#xff09;和世界坐标&#xff08;World Coordinates&#xff09;是描述物体位置的两种不同方式&#xff1a; 局部坐标&#xff08;Local Coordinates&#xff09;&#xff1a; 局部坐标是相对于物体自身的坐标系…

Puppeteer 是什么以及如何在网络抓取中使用它 | 2024 完整指南

网页抓取已经成为任何处理网页数据提取的人都必须掌握的一项重要技能。无论你是开发者、数据科学家还是希望从网站收集信息的爱好者&#xff0c;Puppeteer都是你可以使用的最强大工具之一。本完整指南将深入探讨什么是Puppeteer以及如何有效地在网页抓取中使用它。 Puppeteer简…

日志的编写与线程池的结合

目录 一、认识日志 二、时间的等级划分 三、日志的输出端 3.1 保存至文件 四、日志的部分信息 4.1 日志等级 4.2 日志时间 五、加载日志 六、日志的宏编写 七、ThreadPool Log 一、认识日志 记录事件&#xff1a; 日志用于记录系统运行过程中发生的各种事件&…

Linux硬件中断(IRQ)的基础知识

目录 一、中断的概念1.1 什么是硬件中断1.2 中断类型二、中断处理的工作原理2.1 中断请求2.2 中断向量2.3 中断服务例程(ISR)2.4 上下文切换2.5 中断处理2.6 任务恢复三、中断处理的编程3.1 注册中断处理函数3.2 注销中断处理函数四、中断和系统性能4.1 中断风暴4.2 IRQ亲和性…

创作模板四【创作模板】

xxx【创作模板四】 前言推荐说明最后 前言 2023年7月7日 以下内容源自《【创作模板四】》 仅供学习交流使用 推荐 无 说明 大三下期末考试进行中 【最后】的文案&#xff1a; 当时期末复习总结的文章&#xff0c;很少有人点赞 确实是很失望&#xff0c;只能自娱自乐&…

数据库SQL Server时间函数Datetime

文章目录 SQL Server 提供了一系列用于处理日期和时间的函数&#xff0c;以下是一些常用的时间函数&#xff1a; GETDATE()&#xff1a;返回当前的日期和时间。 SELECT GETDATE();CURRENT_TIMESTAMP&#xff1a;与 GETDATE() 类似&#xff0c;返回当前的日期和时间。 SELECT…

CloudCone服务器2核1G一年只需15刀

CloudCone服务器博主本人已稳定使用一年多了&#xff0c;非常划算且稳定。2核1G一年才15&#xff0c;相比其他厂家2核动辄月付10左右的价格&#xff0c;cloudcone简直没有对手。 但是15刀这些划算的内容无法在官网直接找到。 博主这里记录 cloudcone.top 这个url&#xff0c;…

Linux Vim编辑器全攻略:从入门到精通

引言 简要介绍Vim编辑器的历史、地位及其在Linux及Unix系统中的广泛应用。强调Vim作为程序员和系统管理员的首选工具之一&#xff0c;其强大的文本编辑能力和高效的编辑模式。 Vim基础篇 安装Vim 介绍在不同Linux发行版上安装Vim的方法&#xff08;如使用apt-get、yum、dnf等…

elementui 日历组件el-calendar使用总结

功能&#xff1a; 1.日历可以周视图、月视图切换&#xff1b; 2.点击月视图中日期可以切换到对应周视图&#xff1b; 3.点击周视图查看当日对应数据&#xff1b; 4.周、月视图状态下&#xff0c;点击前后按钮&#xff0c;分别切换对应上下的周、月&#xff1b; 5.点击回到…

算法 —— 高精度(模拟)

目录 加法高精度 两个正整数相加 两个正小数相加 两正数相加 减法高精度 两个正整数相减 两个正小数相减 两正数相减 加减法总结 乘法高精度 两个正整数相乘 两个正小数相乘 乘法总结 加法高精度 题目来源洛谷&#xff1a;P1601 AB Problem&#xff08;高精&#x…

单片机外围设备-EEPROM

eeprom用iic通信。eeprom有几个特点需要关注&#xff1a; 1、可以单字节读写 2、eeprom按页划分存储&#xff0c;不同型号的eeprom的页大小不一致&#xff0c;往eeprom写数据时&#xff0c;如果写到了该页的末尾&#xff0c;会自动从该页的开头继续写&#xff0c;把之前的数据…

如何PR到别人仓库(指定分支,无废话)

如何PR到别人仓库&#xff08;指定分支&#xff09; 记录一下&#xff0c;之前都是直接master分支&#xff0c;现在记录如何pr到别人仓库的其他分支 首先进入别人仓库然后点击fork到自己仓库 步骤&#xff08;以博主自己一个例子为例&#xff09; &#xff08;1&#xff09;…

c++ primer plus 第16章string 类和标准模板库,16.1.3 使用字符串

c primer plus 第16章string 类和标准模板库,16.1.3 使用字符串 c primer plus 第16章string 类和标准模板库,16.1.3 使用字符串 文章目录 c primer plus 第16章string 类和标准模板库,16.1.3 使用字符串16.1.3 使用字符串程序清单16.3 hangman.cpp 16.1.3 使用字符串 现在&a…

【题目/训练】二叉树的创建遍历(递归非递归)

一、根据二叉树创建字符串 思路&#xff1a;在正常前序递归遍历的基础上&#xff0c;单独加上一个考虑到右子树为空的情况&#xff0c;如下&#xff1a;其结果为 1&#xff08;2&#xff08;4&#xff08;5&#xff09;&#xff08;6&#xff09;&#xff09;&#xff09;&…

android11为开机动画添加铃声(语音)

一、碰到的问题 1、第一次开机无铃声 2、开机时铃声和动画不同步&#xff0c;开头的铃声会丢失 3、开机时铃声/动画不能完全播放完 二、解决 以下为添加的patch /开机铃声不同步&#xff0c;语音第一段无声 diff --git a/media/libmediaplayerservice/MediaPlayerService…

馥郁珍藏:品味红酒的层次与细腻

在生活的点滴中&#xff0c;总有一些事物以其不同的魅力&#xff0c;让我们为之驻足&#xff0c;为之沉醉。红酒&#xff0c;便是其中之一。它不仅仅是一种饮品&#xff0c;更是一种情感的寄托&#xff0c;一种生活的艺术。今天&#xff0c;就让我们一起走进红酒的世界&#xf…

原码补码反码移码

符号位为左边的第一位&#xff0c;正数的为0 &#xff0c;负数为1&#xff1b; 正数 原码、反码、补码一致&#xff1b;移码是补码符号位取反。 负数的反码为 符号位不变以原码其他位 取反&#xff0c;补码是反码加1位&#xff0c;移码是补码的符号位取反&#xff1b;

工控主板:搭载海光3300处理器的全国产化工控主板

最近为客户定做了一款全国产化的工控机主板。搭载海光3300核心板的含有丰富接口的工控主板。