[Qt]系统相关-多线程、线程安全问题以及线程的同步机制

目录

一、Qt多线程编程

1.介绍

2.多线程的操作

线程的创建

QThread的常用API

使用案例

3.Qt线程的使用场景

二、线程安全问题

1.互斥锁

介绍

使用案例

2.读写锁

三、线程的同步

1.条件变量

2.信号量


一、Qt多线程编程

1.介绍

        Qt中的多线程的底层原理和注意事项等等内容和Linux中我们描述的多线程本质上是一个东西,不同系统上的多线程库是不一样的,Linux中使用的是pthread第三方库,而Windows又是另一个库,所以在C++编程的时候,考虑到可移植性的问题,通常使用的是C++11的多线程库。

        Qt出现的时候,还没有C++11的标准。但是Qt也是一个跨平台的,所以底层就根据不同的操作系统,利用相应的系统调用来实现线程的创建、调度、同步等功能,但又在此基础上提供了统一的、跨平台的 API,让开发者无需关心不同操作系统之间的差异。不使用系统的原生API还有一个原因就是Linux的原生的多线程API,因为C语言的局限性,所以说API的设计使用上来说非常的不方便,一般实际开发中也很少使用原生的API。

        简单的说,Qt的多线程库就是对不同操作系统系统调用API的一个封装。

2.多线程的操作

线程的创建

        在Qt当中,多线程的处理一般是通过QThread类来实现的。QThread类对象代表一个在应用程序中可以独立控制的一个线程,每一个也就是说QThread对象管理一个线程。Qt中多线程的创建是通过创建一个继承QThread的子类,就相当于创建了一个线程对象,然后通过重写run函数,通过多态的方式实现了指定线程需要执行的代码逻辑。、

        但是在C++当中,使用多态机制是不常见的,大多数都是说用回调函数的方式,将函数地址传递给线程会更快捷一些。而使用多态机制的话,会带来一些运行时候的额外开销,因为在运行,需要执行函数的时候,需要查询虚函数表,找到对应的函数对应的地址再去执行。在一些游戏引擎、AI、高性能服务器等场景需要性能做到极致,所以说不能使用上述的多态机制,但是对于Qt这种客户端的开发,其实不需要太极致的性能追求。

QThread的常用API

        因为子线程都是继承与QThread的,所以说创建出来的子类,也都是可以调用QThread的相关API的。

API接口说明
run()线程的入口函数
start()会调用run()函数,执行线程函数,相当于线程的开关,如果说线程已经运行了,那么什么都不会做。
currentThread()返回一个指向管理当前指向线程的QThread的指针对象。也就是返回一个当前线程的对象指针。
isRunning()判断线程是否在运行,返回值为bool类型

sleep() / msleep() /

usleep()

线程休眠函数

wait()

阻塞线程,直到满足以下任何一个条件:

        与自身线程关联的一个线程已经执行完毕,或者说还没有启动。两者都会返回true

        已经超过了等待时间。函数会返回false(如果设置的是默认值ULONG_MAX,那么永远都不会超时)

该函数与POSIX的pthread_join类似

terminate()执行线程的执行,线程可以立即终止,也可以不立刻终止,这取决于OS的调度策略。
finished()这是一个线程结束之后发出的信号,可以通过该信号实现线程的清理工作
使用案例

        创建一个线程去完成一个倒计时程序。

        在Qt当中,对于空间内部的实现是有些复杂的,那么如果在线程函数中去修改界面上控件的属性内容,可能就会出现线程安全的问题,所以说Qt一刀切,对于控件属性的任何修改都必须在主线程中执行。所以就不能在子线程中去直接修改LCDNumber控件的数值了,但是我们可以在子线程中实现计时的操作,时间每过一秒钟后,就通过信号槽的机制,通知主线程,负责更新界面的内容。

        在创建线程对象的时候,需要将线程对象设置为类变量,否则出了构造函数的局部作用域的话,就会析构销毁了。

//timethread.h文件-----------------------------------#ifndef TIMETHREAD_H
#define TIMETHREAD_H#include <QWidget>
#include <QThread>class TimeThread : public QThread
{Q_OBJECT
public:TimeThread();void run() override;//自定义信号
signals:void thread_signal();};#endif // TIMETHREAD_H//timethread.cpp文件-----------------------------------#include "timethread.h"TimeThread::TimeThread()
{}//重写入口函数
void TimeThread::run()
{//每隔一秒通知一下主线程for(int i = 0; i < 10; i++){sleep(1);//发送信号emit thread_signal();}
}//widget.h文件-------------------------------------#ifndef WIDGET_H
#define WIDGET_H#include <QWidget>
#include <timethread.h>QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACEclass Widget : public QWidget
{Q_OBJECTpublic:Widget(QWidget *parent = nullptr);~Widget();public slots:void handlersignal();private:Ui::Widget *ui;//创建一个线程对象TimeThread thread;
};
#endif // WIDGET_H//widget.cpp文件---------------------------------------#include "widget.h"
#include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);//绑定线程信号connect(&thread, &TimeThread::thread_signal, this, &Widget::handlersignal);//启动线程thread.start();
}Widget::~Widget()
{delete ui;
}void Widget::handlersignal()
{//获取倒计时的值int value = ui->lcdNumber->intValue();if(value <= 0){return;}//更新ui->lcdNumber->display(value - 1);}

3.Qt线程的使用场景

        我们日常在提及C++的多线程的时候,通过都是站在服务器的角度上去思考问题,对于服务器来说,最主要的就是性能高效,基于多核CPU的以及双路CPU(一个主板上有两个CPU)的硬件支持,使用多线程可以充分的利用CPU的资源,提高服务器的效率。

        而客户端上的侧重点,对于普通用户日常使用来说,并不需要那么高的性能,因为对于人来说0.00001秒和0.1秒或者说1s的区别都不算太大。更重要的是一个用户的体验感。如果采用极致效率的多线程编程的话,会将用户设备的CPU计算资源用完,用户的设备就会很卡了。

        更多的使用多线程的场景是在于,采用多线程的方法,执行一些耗时的I/O的操作,避免主线程被卡死而产生不好的体验。例如客户端通常需要和服务器进行网络通信,在下载一些大型文件的时候,需要消耗很长时间,如果我们不采用多线程的话,这种密集的I/O操作相当于是需要等待资源就绪才可以往下运行,那么就会将主线程挂起,不参与CPU的调度执行了,那么用户就无法进行任何操作了。这样的体验感会很差。

        在例如早期的大型PC游戏,在启动的时候需要从服务器或者本地文件当中加载大量的资源数据,如果我们在他加载的时候,狂点鼠标或者键盘的话,是没有任何影响的,而且点几下之后,很可能整个窗口就出问题了,Windows会弹出无法影响操作是否要强制关闭的提示窗口,这样用户就会很蒙,也会大大的降低体验感。(其实是游戏在加载,并非游戏出错卡死)

        基于上述的两个场景来说,最好的方法就是创建子线程去进行密集的I/O操作,这样就算挂起的话,也是挂起子线程,不会影响主线程的继续运行,不影响继续相应用户的操作。

二、线程安全问题

1.互斥锁

介绍

        维护线程安全最有效的方式就是使用锁,将多线程的并发执行转变为串行执行。Qt的锁是对不同系统的底层锁进行了一个封装,封装成了一个QMutex类。对于锁的操作最重要的就是加锁和解锁的操作, QMutex类中提供了lock和unlock成员函数。

        Qt中的锁和C++11标准库提供的锁使用方式类似,只需要创建一个锁对象就可以了执行成员函数了,不需要创建锁,并将锁对象管理起来这些操作。

        上述虽然使用起来已经很方便了,但是对于锁的释放,实际开发中的工程项目逻辑会非常复杂,很容易就忘记释放锁了,或者在中间过程中直接跳转了,或者抛出异常等等场景,都会使得锁资源忘记释放导致死锁了。所以Qt为我们提供了一个QMutexLocker类,该类其实就是QMutex的辅助类,内部使用的是RAII的方式对互斥锁进行上锁和解锁的操作。简化了互斥锁的上锁和解锁操作,避免忘记解锁导致死锁的问题。这个和C++11提供的shared_lock类似。

使用案例

        当我们创建两个线程去执行对一个公共的static变量循环++50000次之后,打印该公共变量发现他的值并非是100000,所以就出现了线程安全问题,在操作++的时候并发访问该变量的值了。所以需要加锁。

//thread.h文件-------------------------------------#ifndef THREAD_H
#define THREAD_H#include <QWidget>
#include <QThread>
#include <QMutex>class Thread : public QThread
{Q_OBJECT
public:Thread();//重写run函数void run() override;public:static int num;static QMutex mutex;
};#endif // THREAD_H//thread.cpp文件-----------------------------------------#include "thread.h"
int Thread::num = 0;
QMutex Thread::mutex;Thread::Thread()
{}void Thread::run()
{for(int i = 0; i < 50000; i++){mutex.lock();num++;mutex.unlock();}
}//widget.cpp文件#include "widget.h"
#include "ui_widget.h"
#include "thread.h"
#include <QDebug>Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);//创建两个线程对象Thread td1;Thread td2;//启动线程td1.start();td2.start();//线程等待td1.wait();td2.wait();//打印结果qDebug() << Thread::num;
}Widget::~Widget()
{delete ui;
}

2.读写锁

        读写锁也是一个非常经典的锁,适用于读写写者问题,该问题描述的是在一个场景中有一个公共资源的区域,写入线程负责写入数据,那么写入线程之间就需要采用互斥的机制,防止数据的紊乱,为了实现这个操作,提供了写锁,该锁就是一个互斥锁,同一时间只允许一个线程访问临界资源。读取线程则是只会读取内容,所以可以多个读取线程并发访问临界资源。对于读取线程也提供了读取锁,但是该锁并不是互斥锁,更多情况下像是一个用来计数的功能。

        Qt中提供了QReadWriteLock类、实现读写锁,同时设计了QReadLock读锁和QWirteLock写锁作为辅助类,配合QReadWriteLock类完成读写线程的同步。该读写锁类中会记录是否有写入线程正在写入,以及是否有读取线程正在读取。

        如果说有写入线程正在写入的话,其他写入线程和读取线程都不可以进行访问临界资源,而如果有读取线程正在读取的话,写入线程就会阻塞等待读取线程读取完毕,读取线程没有任何限制。

        同时为了防止忘记解锁导致死锁问题,Qt也提供了RAII机制的读写锁类QReadWriteLocker、QReadLocker、QWirteLock。

三、线程的同步

        对于线程同步,一句话概括就是让多线程对于共享资源的访问具有一定的顺序性。在多线程的程序中,虽然说可以让计算操作或者I/O操作和主线程分开执行,但是主线程一定是需要计算操作或者I/O操作后的数据,如果说数据没就绪的话,往下执行可能会出现一些意想不到的问题。或者说,两个线程配合执行,所以需要通知对方自己现在的一个状态,也需要设置同步机制,最经典的就是生产消费者模型。

1.条件变量

        Qt中的条件变量类是QWaitCondition类,对于条件变量来说主要就是等待条件就绪,以及唤醒操作,该类内部提供了wait、wakeOne以及wakeAll接口。

2.信号量

        Qt中的信号量类是QSemaphore类,在构造函数中传递信号量的个数,对于申请信号量的操作是acquire接口,释放信号量是release接口。

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

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

相关文章

Linux Bash 中使用重定向运算符的 5 种方法

注&#xff1a;机翻&#xff0c;未校。 Five ways to use redirect operators in Bash Posted: January 22, 2021 | by Damon Garn Redirect operators are a basic but essential part of working at the Bash command line. See how to safely redirect input and output t…

【Linux】环境变量

&#x1f525;个人主页&#x1f525;&#xff1a;孤寂大仙V &#x1f308;收录专栏&#x1f308;&#xff1a;Linux &#x1f339;往期回顾&#x1f339;&#xff1a;【Linux】进程优先级与进程切换 &#x1f516;流水不争&#xff0c;争的是滔滔不 一、环境变量的定义二、命令…

Spring MVC:设置响应

目录 引言 1. 返回静态页面 1.1 Spring 默认扫描路径 1.2 RestController 1.2.1 Controller > 返回页面 1.2.2 ResponseBody 2. 返回 HTML 2.1 RequestMapping 2.1.1 produces(修改响应的 Content-Type) 2.1.2 其他属性 3. 返回 JSON 4. 设置状态码 4.1 HttpSer…

开篇:吴恩达《机器学习》课程及免费旁听方法

课程地址&#xff1a; Machine Learning | Coursera 共包含三个子课程 Supervised Machine Learning: Regression and Classification | Coursera Advanced Learning Algorithms | Coursera Unsupervised Learning, Recommenders, Reinforcement Learning | Coursera 免费…

【C++】模板(进阶)

本篇我们来介绍更多关于C模板的知识。模板初阶移步至&#xff1a;【C】模板&#xff08;初阶&#xff09; 1.非类型模板参数 1.1 非类型模板参数介绍 模板参数可以是类型形参&#xff0c;也可以是非类型形参。类型形参就是我们目前接触到的一些模板参数。 //类型模板参数 …

快手SDK接入错误处理经验总结(WebGL方案)

1、打包时提示Assets\WebGLTemplates\ks路径下未找到Index.html文件错误 处理方法&#xff1a;直接使用Unity默认模板下的Index.html文件即可 文件所在路径&#xff1a;Unity安装路径\Editor\Data\PlaybackEngines\WebGLSupport\BuildTools\WebGLTemplates\Default 参考图&a…

用edge浏览器追剧音量太小?安装音量增强器可解忧

0 源起 春节佳节将至&#xff0c;可以利用这个难得的假期追一追想看而没空看的剧了。 但是在用Edge浏览器播放网页中的视频时&#xff0c;有时音量太小&#xff0c;根本没法听清楚&#xff0c; 遇到这种情况时&#xff0c;尽管Edge浏览器本身没有提供音量控制功能&#xff0…

Alluxio 联手 Solidigm 推出针对 AI 工作负载的高级缓存解决方案

作者&#xff1a;Wayne Gao, Yi Wang, Jie Chen, Sarika Mehta Alluxio 作为全球领先的 AI 缓存解决方案供应商&#xff0c; 提供针对 GPU 驱动 AI 负载的高速缓存。其可扩展架构支持数万个节点&#xff0c;能显著降低存储带宽的消耗。Alluxio 在解决 AI 存储挑战方面的前沿技…

Excel 技巧15 - 在Excel中抠图头像,换背景色(★★)

本文讲了如何在Excel中抠图头像&#xff0c;换背景色。 1&#xff0c;如何在Excel中抠图头像&#xff0c;换背景色 大家都知道在PS中可以很容易抠图头像&#xff0c;换背景色&#xff0c;其实Excel中也可以抠简单的图&#xff0c;换背景色。 ※所用头像图片为百度搜索&#x…

JavaScript笔记基础篇03——函数

黑马程序员视频地址&#xff1a;黑马程序员前端JavaScript入门到精通全套视频教程https://www.bilibili.com/video/BV1Y84y1L7Nn?vd_source0a2d366696f87e241adc64419bf12cab&spm_id_from333.788.videopod.episodes 目录 函数 函数的使用 1.函数的声明语法 2.函数的…

manim(manimgl)安装教学-win11(2024-08)

manim 目前的两种版本&#xff1a;★★ 稍微捋一捋【项目中的 readme.md 十分重要】 manimgl 是 Grant Sanderson&#xff08;YouTube频道 3Blue1Brown的作者&#xff09;等人开发。 现在为 manimgl&#xff0c;在维护中。 manimCE 是2020年后的 manim 分支 manim community e…

常见Arthas命令与实践

Arthas 官网&#xff1a;https://arthas.aliyun.com/doc/&#xff0c;官方文档对 Arthas 的每个命令都做出了介绍和解释&#xff0c;并且还有在线教程&#xff0c;方便学习和熟悉命令。 Arthas Idea 的 IDEA 插件。 这是一款能快速生成 Arthas命令的插件&#xff0c;可快速生成…

DS18B20温度传感器详解(STM32)

目录 一、介绍 二、传感器原理 1.原理图 2.工作时序 3.工作原理&#xff1a;复位脉冲与应答脉冲 4.工作原理&#xff1a;写时序 5.工作原理&#xff1a;读时序 6.工作原理&#xff1a;DS18B20读取的数据格式 7.工作原理&#xff1a;DS18B20配置步骤 三、程序设计 ma…

Chrome远程桌面无法连接怎么解决?

Chrome远程桌面连接已停止工作 Chrome远程桌面是一款极为便捷的浏览器插件&#xff0c;能够帮助用户将自己的计算机连接到其他设备&#xff0c;无论是手机、平板电脑还是其他电脑。然而&#xff0c;在实际使用中&#xff0c;许多用户可能会面临各种各样的问题&#xff0c;比如…

靠右行驶数学建模分析(2014MCM美赛A题)

笔记 题目 要求分析&#xff1a; 比较规则的性能&#xff0c;分为light和heavy两种情况&#xff0c;性能指的是 a.流量与安全 b. 速度限制等分析左侧驾驶分析智能系统 论文 参考论文 两类规则分析 靠右行驶&#xff08;第一条&#xff09;2. 无限制&#xff08;去掉了第一条…

如何实现亿级用户在线状态统计?

亿级用户在线场景分析与解决方案 目录 亿级用户在线场景分析解决方案 2.1 基于总数的统计方案2.2 基于具体用户详情的统计方案 具体实现 3.1 基于总数的统计方案3.2 基于用户标识的统计实现3.3 Spring Boot 中的实现 总结 1. 亿级用户在线场景分析 以 QQ 在线状态统计为例&am…

多线程杂谈:惊群现象、CAS、安全的单例

引言 本文是一篇杂谈&#xff0c;帮助大家了解多线程可能会出现的面试题。 目录 引言 惊群现象 结合条件变量 CAS原子操作&#xff08;cmp & swap&#xff09; 线程控制&#xff1a;两个线程交替打印奇偶数 智能指针线程安全 单例模式线程安全 最简单的单例&…

sql实战解析-sum()over(partition by xx order by xx)

该窗口函数功能 sum( c )over( partition by a order by b) 按照一定规则汇总c的值&#xff0c;具体规则为以a分组&#xff0c;每组内按照b进行排序&#xff0c;汇总第一行至当前行的c的加和值。 从简单开始一步一步讲&#xff0c; 1、sum( )over( ) 对所有行进行求和 2、sum(…

你还在用idea吗

从VIM、Emacs&#xff0c;到eclipse、Jetbrains, 再到VSCode&#xff0c;过去的三十年时间&#xff0c;出现了这三代IDE产品。现在属于AI的时代来了&#xff0c;最新一代的产品像Cursor、Windsurf&#xff0c;就在昨天&#xff0c;字节跳动发布了最新的IDE&#xff0c;就叫Trae…

Unity新版InputSystem短按与长按,改键的实现

目录 前言&#xff1a; 一、InputSystem简介 1.安装InputSystem包 2.创建配置文件 3.创建自定义的Actions 二、自定义输入类 三、改键 四、全代码 前言&#xff1a; 新版inputsystem是Unity推出的一种新的输入方式&#xff0c;它将设备与行为进行分离&#xff0c;通过…