QT多线程(线程互斥)

news2025/1/21 10:15:26

文章目录

  • 前言
  • 一、导致问题产生的原因和解决方法
  • 二、同时访问一个临界资源带来的问题
  • 三、QMutex线程锁
  • 4.线程死锁
  • 5.解决死锁的方法
  • 总结


前言

线程互斥是指在多线程并发执行时,为避免多个线程访问共享资源时发生冲突而采取的一种机制。本篇文章我们就这个问题来了解一下什么叫线程互斥,又如何解决线程互斥的问题。

一、导致问题产生的原因和解决方法

如果多个线程同时访问同一共享资源,可能会导致数据不一致、资源竞争和死锁等问题。

为了避免这些问题,可以使用互斥锁(Mutex)来保护共享资源。互斥锁是一种同步机制,用于控制多个线程对共享资源的访问。当一个线程获得了互斥锁,其他线程就无法获得该锁,直到该线程释放互斥锁为止。

二、同时访问一个临界资源带来的问题

下面我们编写一个示例程序来带大家详细的看一下同时访问一个临界资源带来的问题。

下面的代码定义了一个临时变量g_i,同时创建了一个Mythread线程,在代码中让Mythread和主线程去访问这个临界资源让这个变量增加。

static int g_i = 0;//临界资源

class Mythread : public QThread
{
protected:
    void run()
    {
        for(int i = 0; i < 5; i++)
        {
            g_i++;
            qDebug() << "Mythread g_i :" << g_i;
            sleep(1);   //休眠1s
        }
    }
};

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    Mythread t1;

    t1.start();

    for(int i = 0; i < 5; i++)
    {
        g_i++;
        qDebug() << "Main Thread g_i :" << g_i;
        QThread::sleep(1);   //休眠1s
    }


    return a.exec();
}

运行结果:
可以看到这个打印结果看起来是非常奇怪的。这就是因为两个线程同时访问了一个变量导致的问题,那么下面使用锁来解决这个问题。
在这里插入图片描述

三、QMutex线程锁

QMutex是Qt框架中提供的互斥锁类,用于保护共享资源以避免多个线程同时访问同一共享资源导致的竞争问题。

QMutex的使用非常简单,基本步骤如下:

1.创建QMutex对象

QMutex mutex;

2.在访问共享资源的代码段前加锁

mutex.lock();
// Access shared resource

3.在访问共享资源的代码段后解锁

// Access shared resource
mutex.unlock();

使用线程锁解决上述的问题:

当进行访问或者使用临界资源时需要对其进行上锁操作,当使用结束后再解锁,这样就可以避免竞争同一个临界资源带来的问题。

static QMutex g_mutex;

static int g_i = 0;//临界资源

class Mythread : public QThread
{
protected:
    void run()
    {
        for(int i = 0; i < 5; i++)
        {
            g_mutex.lock();
            g_i++;
            qDebug() << "Mythread g_i :" << g_i;
            g_mutex.unlock();
            sleep(1);   //休眠1s
        }
    }
};

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    Mythread t1;

    t1.start();

    for(int i = 0; i < 5; i++)
    {
        g_mutex.lock();
        g_i++;
        qDebug() << "Main Thread g_i :" << g_i;
        g_mutex.unlock();
        QThread::sleep(1);   //休眠1s
    }


    return a.exec();
}

运行结果:
在这里插入图片描述

4.线程死锁

线程死锁(Deadlock)是指两个或多个线程在执行过程中因争夺资源而造成的一种互相等待的现象,导致所有线程都被阻塞,无法继续执行。

在多线程编程中,线程死锁是一个非常常见的问题,一旦发生死锁,程序将永远无法继续执行下去,通常需要手动结束程序。线程死锁的发生通常由于多个线程之间互相等待对方释放资源,从而导致所有线程都无法执行下去。

线程死锁往往是由于以下几个因素引起的:

1.互斥:多个线程同时访问共享资源,但只能有一个线程占用该资源,其它线程必须等待。

2.不可抢占:资源在被一个线程占用时,不能被其它线程强制抢占。

3.持有和等待:一个线程持有一个资源且正在等待另外一个线程释放它所持有的资源。

4.环路等待:一组线程互相等待,并且每个线程都在等待另一个线程释放资源。

这里给出一个死锁的例子:

static QMutex g_mutex1;
static QMutex g_mutex2;

static int g_i = 0;//临界资源
static int g_i1 = 0;//临界资源

class Mythread : public QThread
{
protected:
    void run()
    {
        for(int i = 0; i < 5; i++)
        {
            g_mutex1.lock();
            g_mutex2.lock();
            g_i++;
            g_i1++;
            qDebug() << "Mythread g_i :" << g_i;
            g_mutex2.unlock();
            g_mutex1.lock();
            sleep(1);   //休眠1s
        }
    }
};

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    Mythread t1;

    t1.start();

    for(int i = 0; i < 5; i++)
    {
        g_mutex2.lock();
        g_mutex1.lock();
        g_i++;
        g_i1++;
        qDebug() << "Main Thread g_i :" << g_i;
        g_mutex1.unlock();
        g_mutex2.unlock();
        QThread::sleep(1);   //休眠1s
    }


    return a.exec();
}

运行结果:

这里可以看到两个线程分别只运行了一次就卡死了。
在这里插入图片描述
程序产生死锁的原因:
主线程和被创建出来的线程开始运行并获取线程锁,主线程获取线程锁2,被创建出的线程获取线程锁1。
当被创建出的线程想获取线程锁2时会发现无法获取线程锁2,因为此时线程锁2被主线程获取了,当主线程想获取线程锁1时也是同样的道理,所有这就导致了线程的死锁。

假设线程1先获取了g_mutex1这个互斥锁,线程2先获取了g_mutex2这个互斥锁。然后线程1又试图获取g_mutex2这个互斥锁,此时它会一直等待线程2释放该互斥锁;同时,线程2也试图获取g_mutex1这个互斥锁,由于该锁已经被线程1占用,线程2也一直等待。这样,线程1和线程2就互相等待对方释放锁,导致死锁。

5.解决死锁的方法

给每一个临界资源都分配一个编号。
给每一个线程锁都分配一个编号。
一个线程锁对应一个临界资源。

每一个线程按照顺序获取线程锁。

解决代码:

QMutex g_mutex_1;
QMutex g_mutex_2;

class ThreadA : public QThread
{
protected:
    void run()
    {
        while( true )
        {
            g_mutex_1.lock();

            qDebug() << objectName() << "get m1";

            g_mutex_2.lock();

            qDebug() << objectName() << "get m2";

            qDebug() << objectName() << "do work ...";

            g_mutex_2.unlock();
            g_mutex_1.unlock();

            sleep(1);
        }
    }
};

class ThreadB : public QThread
{
protected:
    void run()
    {
        while( true )
        {
            g_mutex_1.lock();

            qDebug() << objectName() << "get m2";

            g_mutex_2.lock();

            qDebug() << objectName() << "get m1";

            qDebug() << objectName() << "do work ...";

            g_mutex_2.unlock();
            g_mutex_1.unlock();

            sleep(1);
        }
    }
};

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    ThreadA ta;
    ThreadB tb;

    ta.setObjectName("ta");
    tb.setObjectName("tb");

    ta.start();
    tb.start();

    return a.exec();
}

总结

这篇文章讲解了线程的互斥和线程的死锁,并给出了线程死锁的解决方法。

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

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

相关文章

c++学习之mystring的简单封装

我们经常利用string类实例化对象来对字符串进行各种操作&#xff0c;string类是一个实用的类&#xff0c;那么对于string类的一些基本操作是如何实现的呢&#xff1f;我们简单的实现一下mystring的封装。 目录 1.常用的字符串函数 2.构造函数的创建 1.无参构造 2.有参构造…

Java面试知识点(全)- Java面试基础部分二

[Java面试知识点(全)(https://nanxiang.blog.csdn.net/article/details/130640392)&#xff1a; 导航&#xff1a; https://nanxiang.blog.csdn.net/article/details/130640392 注&#xff1a;随时更新 TPS&QPS TPS&#xff1a;是Transactions PerSecond的缩写&#xff0…

elment-ui/plus不定高度容器收缩折叠动画组件

文章目录 学习链接效果代码 学习链接 原生js手动实现一个多级菜单效果&#xff08;高度可过渡变化&#xff09; - 自己的链接 vue实现折叠展开收缩动画 - 自己的链接 效果 代码 在使用element-plus的折叠组件的时候&#xff0c;一般用的是<el-collapse>组件&#xff0…

Vector - CAPL - CANoe硬件配置函数 - 04

目录 getChipType -- 确定当前所用的CAN控制器类型 代码示例 setCanCabsMode -- 设置 CANcab 模式 代码示例 setOcr -- 设置输出寄存器 代码示例 setBtr -- 设置位时序寄存器BTR 代码示例 getChipType -- 确定当前所用的CAN控制器类型 功能&#xff1a;确定所用 CAN 控制…

身边不动点定理的有意思应用

最近读高观点下的数学这本书&#xff0c;对书中介绍的布劳威尔不动点定理的有趣性质印象很深&#xff0c;原因是这个定理的某些性质能够解释我们生活中的一些常见现象&#xff0c;这里结合一个例题&#xff0c;聊以记录。 从一个数学题讲起&#xff1a; f(x)是定义在[0,1]上的…

Netty 服务端开发及性能优化

Netty 是一个异步基于事件驱动的高性能网络通信框架&#xff0c;可以看做是对 NIO 和 BIO 的封装&#xff0c;并提供了简单易用的 API、Handler 和工具类等&#xff0c;用以快速开发高性能、高可靠性的网络服务端和客户端程序。 1. 创建服务端 服务端启动需要创建 ServerBoot…

面试官说,Java中的volatile关键字有什么作用?

在Java中&#xff0c;volatile是一个关键字&#xff0c;它用于标记变量&#xff0c;以指示该变量可能随时被多个线程访问并修改。从面试的角度来看&#xff0c;了解volatile关键字的作用和原理对于Java开发人员来说非常重要。在本文中&#xff0c;我将详细讲解volatile关键字的…

springboot+vue班级综合测评管理系统(源码+文档)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的班级综合测评管理系统。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 &#x1f495;&#x1f495;作者&#xff1…

prometheus中通过node-exporter中的--collector.textfile.directory这一选项参数自定义监控指标

简述node-exporter中该选项参数的作用。 --collector.textfile.directory 是 Node Exporter 的一个命令行选项&#xff0c;用于指定从 textfile 收集器中收集数据的目录。 Node Exporter 会通过各种方式收集主机的度量值&#xff0c;并将这些度量值暴露给 Prometheus。其中之…

【嵌入式烧录刷写文件】-2.3-删除/修改Intel Hex文件中指定地址范围内的数据

案例背景&#xff08;共6页精讲&#xff09;&#xff1a; 有如下一段HEX文件&#xff0c;如何“自动”地完成地址范围0x9110-0x9113数据的删除或修改。 :2091000058595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F70717273747576775F :2091200078797A7B7C7D7E7F808182838485…

C++ STL之 list 基础

文章目录 前言STL之list基础知识1. list的介绍2. list的使用2.1 list的构造2.2 list iterator的使用2.3 空间相关2.4 元素访问2.5 相关函数2.6 list的迭代器失效 3. list与vector的对比 后记 前言 本篇将学习 list 的基础知识 &#x1f57a;作者&#xff1a; 迷茫的启明星 专栏…

网络安全从业人员职业发展和规划

1、为什么做这次分享&#xff1f; 2、人生周期三模型 3、职业生涯阶段划分 4、通用职业发展路径 5、当前安全行业前景如何&#xff1f; 6、安全就业行情如何&#xff1f; 7、安全行业就业市场岗位划分 8、什么是相对比较好的履历&#xff1f; 9、选择甲方还是选择乙方&#xf…

Prompt learning 教学[技巧篇]:通过增加示例、引导词、特殊符号指令等方式让chatgpt输出更好的答案

Prompt learning 教学[技巧篇]&#xff1a;通过增加示例、引导词、特殊符号指令等方式让chatgpt输出更好的答案 技巧1&#xff1a;To Do and Not To Do 在问答场景里&#xff0c;为了让 AI 回答更加准确&#xff0c;一般会在问题里加条件。比如让 AI 推荐一部电影给你 Recomme…

【高数+复变函数】傅里叶变换

文章目录 【高数复变函数】傅里叶变换3 傅里叶变换3.1 基本概念3.2 单位脉冲函数及其傅里叶变换3.3 非周期函数的频谱 上一节 【高数复变函数】傅里叶积分 【高数复变函数】傅里叶变换 3 傅里叶变换 3.1 基本概念 回顾&#xff1a;上一节中的Fourier积分公式 f ( t ) 1 2…

前端二进制流的关系

Blob 全称&#xff1a;binary large object&#xff0c;二进制大对象&#xff0c;是一个js对象&#xff0c;可以用来存储大量二进制编码格式的数据&#xff0c;Blob对象是不可修改的&#xff0c;读取内容的唯一方法是FileReader。 创建一个Blob对象&#xff1a; new Blob(ar…

SSM(Vue3+ElementPlus+Axios+SSM前后端分离)--后端实现

目录 SSM(Vue3ElementPlusAxiosSSM前后端分离)--后端实现 技术栈 实现功能04-添加家居信息 需求分析/图解 思路分析 代码实现 创建\service\FurnService.java 和\service\FurnServiceImpl.java, 增加添加方法 修改Furn.java , 当创建Furn 对象imgPath 为null 时, imgPa…

Java14-常用类:字符串,日期类,比较器

一&#xff1a;字符串&#xff1a;String 1.概述&#xff1a; String&#xff1a;字符串&#xff0c;使用一对""引起来表示。 1.String 声明 为final的&#xff0c;不可被继承 2.String 实现了Serializable接口&#xff1a;表示字符串是支持序列化的。 实现了Co…

多线程相关高频面试题

一、线程的基础知识 1、线程和进程的区别&#xff1f; 进程是正在运行程序的实例&#xff0c;进程中包含了线程&#xff0c;每个线程执行不同的任务。不同的进程使用不同的内存空间&#xff0c;在当前进程下的所有线程可以共享内存空间。线程更轻量&#xff0c;线程上下文切换…

GPIO口输出与输入模式的理解

问题&#xff1f;看GPIO的结构图&#xff0c;发现&#xff1a; 上拉输入电流是从引脚流入外部 下来输入电流是从引脚流进芯片内部 推挽输出推模式电流是从引脚流入外部 推挽输出挽模式电流是从外部流入内部 输入输出模式都有电流流入流出&#xff0c;为什么还要分为输入输出模…

K8s排错之浏览器打不开K8s Dashboard

一、问题 10.0.0.10 通常会使用加密技术来保护您的信息。Chrome 此次尝试连接到 10.0.0.10 时&#xff0c;该网站发回了异常的错误凭据。这可能是因为有攻击者在试图冒充 10.0.0.10&#xff0c;或者 Wi-Fi 登录屏幕中断了此次连接。请放心&#xff0c;您的信息仍然是安全的&am…