文章目录

      • 1. 互斥
        • 1.1 为什么需要互斥
        • 1.2 互斥锁
        • 1.3 初谈互斥与同步
        • 1.4 锁的原理
        • 1.5 可重入VS线程安全
        • 1.6 死锁
        • 1.7 避免死锁的算法(扩展)

  • 序:在上一章中我们知道了线程控制的三个角度:线程创建、线程等待和线程终止,分别从接口以及参数的意义和功能的角度来了解,以及最后深入原生线程库,了解用户级线程与内核轻量型进程的关系。而本章将从线程的同步与互斥的角度来带大家了解什么是互斥和同步,以及为什么要互斥和同步等一系列问题。

上一章线程控制的知识补充:

线程分离:
在这里插入图片描述
pthread_detach函数,可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离,分离后的线程不可被等待,如果强行等待也会返回错误码22。

问题一:为什么要有线程分离呢?

如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。

归根结底,我们让线程分离,其实就是更改线程的原生线程库里的tcb内的分离的属性,而pthread_join就是识别到了该分离属性被更改为已分离,所以才会直接返回一个错误码。

1. 互斥

1.1 为什么需要互斥

多线程抢票模型代码演示:

#include<iostream>
#include<unistd.h>
#include<pthread.h>
#include<string>
#include<vector>
using namespace std;#define NUM 4int ticket =100;//用多线程,模拟一轮抢票class ThreadData
{
public:ThreadData(int number){_thread_name ="thread-" + to_string(number);}
public:string _thread_name;
};void* GetTicket(void* args)
{ThreadData* td=static_cast<ThreadData*>(args);const char* name =td->_thread_name.c_str();while(true){if(ticket>0){usleep(5000);printf("i am %s,get a ticket:%d\n",name,ticket);ticket--;}else break;}printf("%s ... quit\n",name);return nullptr;}
int main()
{vector<pthread_t> tids;vector<ThreadData*> thread_datas;for(int i=0;i<NUM;i++){pthread_t tid;ThreadData* td=new ThreadData(i);thread_datas.push_back(td);pthread_create(&tid,nullptr,GetTicket,thread_datas[i]);tids.push_back(tid);}for(auto &e :tids){pthread_join(e,nullptr);}for(auto &e :thread_datas){delete e;}return 0;
}

结果如图:

在这里插入图片描述
那么问题来了抢票模型中,为什么抢票抢到最后,竟然抢到了负数?这个问题我们暂且不谈,我们继续往下说。

要想了解为什么会出现这样的情况,我们首先就要知道既然ticket出现了负数,就说明ticket–出现了问题,共享数据------>数据不一致问题!!!(肯定和多线程并发访问是有关系的),对一个全局变量进行多线程并发–或++操作是否是安全的?所以这个–操作不是原子的,所以也不是安全的。

既然–操作是不安全的,不是原子的,那我们要了解ticket–究竟要有哪些步骤:
在这里插入图片描述

第一个步骤:先将ticket读入到CPU的寄存器当中
第二个步骤:CPU内部进行–操作
第三个步骤:将计算结果写回内存

但是想要了解为什么会出现这样的情况,我们还要了解一个额外的知识点:寄存器不等于寄存器的内容线程在执行的时候,将共享数据,加载到CPU寄存器的本质:把数据的内容,变成了自己的上下文 — 这样的数据以拷贝的形式给自己单独拿了一份

既然ticket–是有三个步骤组成,如果在这三个步骤之内发生了线程切换就会导致数据不一致的问题!!!

在这里插入图片描述
假设当前抢票的进程中,票有1000张,该进程内有两个线程正在抢票,此时thread-1线程正在实现抢票,刚完成第一步,将内存中的数据读入寄存器中,也就是读入该线程的上下文中,如果此时来了第二个线程thread-2也要实行抢票,将thread-1线程切换了,thread-1线程就会带着这个1000的数据一起离开,等待线程再次切换回来!!!
在这里插入图片描述
当线程切换到thread-2线程,假设此时thread-2线程在下一次线程切换的时间片内进行了100次抢票的动作,此时的票数就由1000变成了900
在这里插入图片描述

当thread-2线程抢了100张票后,将寄存中的900写回给内存中的ticket,此时thread-1线程切换回来了,thread-2线程就要带着自己的硬件上下文走,于是thread-2就将寄存器中的900带走了,thread-1线程切换回来后,会将之前就带走的寄存器中的1000带回来,在放入到寄存器中,即恢复上下文。
在这里插入图片描述
然后,此时thread-1会继续执行未完成的动作,继续执行第二步和第三步。
在这里插入图片描述
这就会导致,thread-2辛辛苦苦抢的票,将1000变成900,结果thread-1线程切换回来后就变成了999。

现在让我们来回答最开始的那个问题,为什么ticket会出现负数?
在这里插入图片描述
假设此时的实际票数小于线程数,此时有四个线程,但票只有两个了,别忘了ticket>0也是运算(逻辑运算)所以此时同时有4个线程都对ticket进行了逻辑运算,此时票有两个,都是大于0,此时四个线程都进入了该循环内,都可以进行ticket–,这也就是为什么会出现了负数!!!

1.2 互斥锁

怎么解决上述的一些列问题???
对共享数据的任何访问,保证任何时候只有一个执行流进行访问!—互斥!!!
而想要实现互斥就要引入互斥锁的概念!!!

在这里插入图片描述

锁资源的定义,初始化和释放:

pthread_mutex_t是库提供的一种数据类型
pthread_mutex_init(第一个参数传锁,第二个参数传锁的各种参数(默认传nullptr))
pthread_mutex_destroy
(如果用pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALZER进行全局变量的初始化,就不用调用pthread_mutex_destroy函数进行释放)

一种临界资源,由多个线程访问,如果想要保证临界资源的安全,就必须让这个多个线程访问同一把锁!!!

在这里插入图片描述

锁的申请和释放:

pthread_mutex_lock
pthread_mutex_unlock
其中的pthread_mutex_trylock函数就是加锁的非阻塞版本

到这一步,大家只能对锁有个印象,没法深刻知道锁的作用和锁的使用,接下来我将改进原本的多线程抢票模型的代码,用互斥锁来使原版中的多线程导致的数据不一致问题得到解决!!!

互斥锁版的多线程抢票模型代码演示

#include<iostream>
#include<unistd.h>
#include<pthread.h>
#include<string>
#include<vector>using namespace std;#define NUM 5int ticket =100;//用多线程,模拟一轮抢票class ThreadData
{
public:ThreadData(int number,pthread_mutex_t* lock){_thread_name ="thread-" + to_string(number);_lock=lock;}
public:string _thread_name;pthread_mutex_t *_lock;
};void* GetTicket(void* args)
{ThreadData* td=static_cast<ThreadData*>(args);const char* name =td->_thread_name.c_str();while(true){pthread_mutex_lock(td->_lock);if(ticket > 0){//usleep(5000);printf("i am %s,get a ticket:%d\n",name,ticket);ticket--;}else{pthread_mutex_unlock(td->_lock);break;}pthread_mutex_unlock(td->_lock);usleep(5000);}printf("%s ... quit\n",name);return nullptr;
}int main()
{pthread_mutex_t lock;pthread_mutex_init(&lock,nullptr);vector<pthread_t> tids;vector<ThreadData*> thread_datas;for(int i=0;i<NUM;i++){pthread_t tid;ThreadData* td=new ThreadData(i,&lock);thread_datas.push_back(td);pthread_create(&tid,nullptr,GetTicket,thread_datas[i]);tids.push_back(tid);}for(auto &e :tids){pthread_join(e,nullptr);}for(auto &e :thread_datas){delete e;}pthread_mutex_destroy(&lock);return 0;
}

对于上面一段代码,我们用锁将临界资源锁住,同一时间只能有一个线程进行访问,从而实现对临界资源的保护

其中,被锁保护的资源叫做临界资源,某几段访问临界资源的代码区叫做临界区

加锁的本质:是用时间来换安全
加锁的表现:线程对临界区代码的串行执行
加锁原则:尽量保证临界区代码越少越好。
申请锁成功了,才能往后执行,不成功,就会阻塞等待(等待锁资源释放)

不同线程对于锁的竞争能力可能会不同,在纯互斥环境中,如果锁分配不够合理,容易导致其他线程的饥饿问题----->不是说只要有互斥就必有饥饿,适合纯互斥场景就用互斥。

1.3 初谈互斥与同步

目前,我们对于锁的概念已经有了一个清晰的认识了,但是我们发现了一个新的问题,当一个线程申请锁,完成对临界资源的访问后,释放锁后,该线程可能也会申请锁,这就可能出现一个线程一直在申请和释放锁,导致其他线程没办法申请到锁,对于这种情况,就要深入了解同步的概念来解决新出现的问题,现在让我们更加深入的了解互斥与同步吧

现在有一个vip自习室:
在这里插入图片描述
vip自习室规定:1. 外面的同学想要进入vip自习室必须排队。2. 出来的同学,将钥匙放好后,不能立马重新拿钥匙进vip自习室,如果想要再次进入自习室则必须排到队列的尾部进行排队

vip自习室的规则让所有的同学(线程)按照一定的顺序拿到钥匙(锁)进入vip自习室,而按照一定的顺序性获取资源的模式就是同步!!!

1.4 锁的原理

锁本身也是一种临界资源!!!所以。申请锁和释放锁本身就被设计成为了原子性操作了(问题:如何做到的???)

在临界区中,线程可以被切换吗?可以切换!!!在线程被切出去的时候,是持有锁被切走的。该线程即使被切换走了,照样没有任何线程能进入资源临界区访问临界资源!

对于其他线程来讲,一个线程要么没有锁,要么释放锁。当前线程访问临界区的过程,对于其他线程就是原子的!!!

问题一:为什么说ticket–不是原子的?因为该语句会变成多条汇编语句,在该汇编语句的中间,如果有其他线程也在执行,就会出错,出现不一致的情况,换言之,只要汇编语句只有一条就没有问题,所以,原子:只有一条汇编语句就是原子的!!!

lock的汇编语句:xchqb %al,mutex
Xchqb交换的本质:把内存中的数据(共享),交换到CPU的寄存器中(把数据交换到线程的硬件的上下文中)—>把一个共享的锁,让一个线程以一条汇编语句的方式,交换到自己的上下文中!!!(这就叫做当前线程持有锁了!!!)

unlock的汇编语句:movb $1,mutex:将一个共享的锁从线程的上下文中拿出来。(这就叫做当前线程释放锁了!!!)

1.5 可重入VS线程安全

线程安全的概念:多个线程并发同一段代码时,不会出现不同的结果。常见对全局变量或者静态变量进行操作, 并且没有有锁保护的情况下,会出现该问题。
重入的概念:同一个函数被不同的执行流调用,当前一个流程还没有执行完,就有其他的执行流再次进入,我们称之为重入。一个函数在重入的情况下,运行结果不会出现任何不同或者任何问题,则该函数被称为可重入函数、不则具不可重入函数。

可重入与线程安全联系:

函数是可重入的,那就是线程安全的。
函数是不可重入的,那就不能由多个线程使用,有可能引发线程安全问题。
如果一个函数中有全局变量,那么这个函数既不是线程安全也不是可重入的。

可重入与线程安全区别:

可重入函数是线程安全函数的一种。
线程安全不一定是可重入的,而可重入函数则一定是线程安全的。
如果将对临界资源的访问加上锁,则这个函数是线程安全的,但如果这个重入函数若锁还未释放则会产生死锁,因此是不可重入的。

1.6 死锁

死锁的概念:

死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所站用不会释放的资源而处于的一种永久等待状态。

死锁产生的必要条件:

互斥条件:一个资源每次只能被一个执行流使用(前提)
请求与保持条件:一个执行流因请求资源而阻塞时,对已获得的资源保持不放(原则)
不剥夺条件:一个执行流已获得的资源,在末使用完之前,不能强行剥夺(原则)
死锁产生的充分条件:
循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源的关系(重要条件)
这三个必要条件必须同时满足才是死锁。

如何避免死锁问题?

1. 破坏死锁的四个必要条件,只需要一个不满足就可以了
2. 加锁顺序一致
3. 避免锁未释放的场景
4. 资源一次性分配

1.7 避免死锁的算法(扩展)

银行家算法:

下面是银行家算法的模拟实现,感兴趣的小伙伴可以去了解

银行家算法避免死锁

总结:

本篇博客先是补充了上一章中对于线程分离的知识缺失的内容补全,而后,从一小段代码出发,在多线程的抢票模型下,我们逐步发现多线程带来的问题,并逐步解决,为了解决这些问题,我们先后引入了互斥和同步的概念,最后有队线程安全问题和可重入的问题进行了了解,并讲述了死锁的概念及其产生的条件,最后以避免死锁的银行家算法结尾,谢谢大家的支持!!!

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

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

相关文章

适用于 vue2、vue3 的自定义指定:v-int(正整数)

在项目中&#xff0c;我们经常会遇到输入框只允许输入数字的情况&#xff0c;下面是一段自定义指定 代码&#xff0c;复制到项目中&#xff0c;注册指定即可使用用法如下&#xff1a; 创建一个IntInput.js 文件&#xff0c;将下面代码复制到文件中保存在项目中的 main.js 文件中…

学习基于springboot秒杀系统-环境配置(接口封装,mybatis,mysql,redis(Linux))

文章目录前言创建springboot项目封装controller层输入输出rest api 的json输出返回页面集成mybatis集成redis下载虚拟机和centos下载redis.tar.gz上传redis.tar.gz 到虚拟机前言 今天开始记录学习秒杀系统-课程是基于慕课上的搜索秒杀系统的课程&#xff0c;老师讲解非常好。这…

stm32达到什么程度叫精通?

STM32达到什么程度叫精通&#xff1f;一个十年老兵的深度反思 前言&#xff1a;精通二字&#xff0c;重如泰山 每次有人问我"STM32达到什么程度叫精通"这个问题&#xff0c;我都会沉默很久。 不是因为这个问题难回答&#xff0c;而是因为"精通"这两个字太重…

微软上线Deep Research:OpenAI同款智能体,o3+必应双王炸

今天凌晨&#xff0c;微软在官网宣布&#xff0c;Azure AI Foundry中上线Deep Research公开预览版。这是支持API和SDK的OpenAI 高级智能体研究能力产品&#xff0c;并且Azure 的企业级智能体平台完全集成。Deep Research是OpenAI在今年4月25日发布的最新产品&#xff0c;能够像…

Spring Batch终极指南:原理、实战与性能优化

&#x1f31f; Spring Batch终极指南&#xff1a;原理、实战与性能优化单机日处理10亿数据&#xff1f;揭秘企业级批处理架构的核心引擎&#xff01;一、Spring Batch 究竟是什么&#xff1f;Spring batch是用于创建批处理应用程序&#xff08;执行一系列作业&#xff09;的开源…

【Part 3 Unity VR眼镜端播放器开发与优化】第四节|高分辨率VR全景视频播放性能优化

文章目录《VR 360全景视频开发》专栏Part 3&#xff5c;Unity VR眼镜端播放器开发与优化第一节&#xff5c;基于Unity的360全景视频播放实现方案第二节&#xff5c;VR眼镜端的开发适配与交互设计第三节&#xff5c;Unity VR手势交互开发与深度优化第四节&#xff5c;高分辨率V…

TCP/IP协议基础

TCPIP协议基础 网络模型 -OSI参考模型 -OSI参考模型各层功能 -TCP/IP网络模型 -TCP/IP协议栈OSI参考模型 – 为了解决网络设备之间的兼容性问题&#xff0c;国际标准化组织ISO于1984年提出了OSI RM&#xff08;开放系统互连参考模型&#xff09;。 OSI参考模型一共有七层&#…

【Nginx】Nginx代理WebSocket

1.websocketWebSocket 是一种网络通信协议&#xff0c;它提供了在单个 TCP 连接上进行全双工&#xff08;双向&#xff09;通信的能力假设需求&#xff1a;把 ws://192.168.0.1:8088/ws-api/websocket/pushData代理到ws://192.168.0.156:8888/websocket/pushData&#xff1b;同…

Spring AI Alibaba Graph使用案例人类反馈

1、Spring AI Alibaba Graph 是社区核心实现之一&#xff0c;也是整个框架在设计理念上区别于 Spring AI 只做底层原子抽象的地方&#xff0c;Spring AI Alibaba 期望帮助开发者更容易的构建智能体应用。基于 Graph 开发者可以构建工作流、多智能体应用。Spring AI Alibaba Gra…

本地部署jenkins持续集成

一、准备环境&#xff08;jdk版本跟Tomcat版本要匹配&#xff09; java jdk 环境(版本是11.0.21) jenkins war包(版本是2.440.3) Tomcat (版本是 9.0.84) 二、安装步骤 1、安装jdk环境 1&#xff09;先安装java环境&#xff0c;安装完成后配置环境变量&#xff0c;参考上…

基于Java+Maven+Testng+Selenium+Log4j+Allure+Jenkins搭建一个WebUI自动化框架(1)搭建框架基本雏形

本次框架使用Maven作为代码构建管理&#xff0c;引用了PO模式&#xff0c;将整体的代码分成了页面层、用例层、业务逻辑层。框架搭建流程&#xff1a;1、在pom.xml中引入依赖&#xff1a;<!-- https://mvnrepository.com/artifact/io.appium/java-client --> <depende…

从零构建MCP服务器:FastMCP实战指南

引言&#xff1a;MCP协议与FastMCP框架 Model Context Protocol&#xff08;MCP&#xff09;是连接AI模型与外部服务的标准化协议&#xff0c;允许LLM&#xff08;如Claude、Gemini&#xff09;调用工具、访问数据。然而&#xff0c;直接实现MCP协议需要处理JSON-RPC、会话管理…

基于FPGA的智能小车设计(包含代码)/ 全栈FPGA智能小车:Verilog实现蓝牙/语音/多传感器融合的移动平台

首先先声明一下&#xff0c;本项目已经历多轮测试&#xff0c;可以放心根据我的设计进行二次开发和直接套用&#xff01;&#xff01;&#xff01; 代码有详细的注释&#xff0c;方便同学进行学习&#xff01;&#xff01; 制作不易&#xff0c;记得三连哦&#xff0c;给我动…

Object.defineProperties 详解

Object.defineProperties 详解 Object.defineProperties 是 JavaScript 中用于在一个对象上定义或修改多个属性的方法。它是 Object.defineProperty 的复数版本&#xff0c;允许你一次性定义多个属性。 基本语法 Object.defineProperties(obj, props)obj&#xff1a;要在其上定…

MyBatis-Plus:深入探索与最佳实践

MyBatis-Plus作为MyBatis的增强版&#xff0c;已经在Java开发中得到了广泛应用。它不仅继承了MyBatis的所有功能&#xff0c;还提供了许多强大的扩展功能&#xff0c;帮助开发者提升开发效率和代码质量。本文将深入探讨MyBatis-Plus的高级特性及其在实际项目中的最佳实践。一、…

劳斯莱斯数字孪生技术:重构航空发动机运维的绿色革命

在航空工业迈向智能化的浪潮中&#xff0c;劳斯莱斯以数字孪生技术为核心&#xff0c;构建了发动机全生命周期管理的创新范式。这项技术不仅重新定义了航空发动机的维护策略&#xff0c;更通过数据驱动的决策体系&#xff0c;实现了运营效率与生态效益的双重突破。本文将从技术…

NPM组件 querypilot 等窃取主机敏感信息

【高危】NPM组件 querypilot 等窃取主机敏感信息 漏洞描述 当用户安装受影响版本的 querypilot 等NPM组件包时会窃取用户的主机名、用户名、工作目录、IP地址等信息并发送到攻击者可控的服务器地址。 MPS编号MPS-2kgq-v17b处置建议强烈建议修复发现时间2025-07-05投毒仓库np…

创业商业融资计划书PPT模版

创业商业融资计划书PPT模版&#xff1a;https://pan.quark.cn/s/25a043e4339e

解决GitHub仓库推送子文件夹后打不开的问题

从你描述的情况来看&#xff0c;IELTS_AI_Assessment 很可能被识别为了 Git 子模块&#xff08;submodule&#xff09;&#xff0c;而不是普通文件夹&#xff0c;这会导致在 GitHub 上无法直接打开查看内容。以下是具体原因和解决办法&#xff1a;为什么文件夹无法打开&#xf…