【Linux】互斥 | 死锁

线程互斥

一些概念

  • 临界资源:多线程之间共享的资源就是临界资源。通常为一些全局的变量。
  • 临界区:访问或者修改临界资源的代码就是临界区。
  • 互斥:任何时刻,保证只有一个执行流访问临界资源。
  • 原子性:不受调度机制打断的操作。操作要么完成,要么就是未完成,一步到位。

锁的背景

编写一个简单的多线程抢票功能

设置有1万张票,4个线程分别抢票

tiket就是一个全局变量,可以被每一个线程访问修改。

在票0时刻,抢票时候应该停止

而我们编写的多线程抢票缺导致抢到负数票


解释概念

临界资源:全局的票就是临界资源,被线程共享。

临界区:访问全局变量票的代码

原子性:实际上 tikets--  操作不是一步就完成,会受到调度算法的影响。

为什么会产生负数?

假设有俩个线程 A 和 B

线程A 和 B对一个全局变量g_val=10都做++操作

一个变量做++ 要分三步:先把变量从内存读到CPU; 在CPU里对变量+1 ;将加后的值写到内存中

假设现在线程A的时间片,线程A将内存中10读到CPU寄存器中,并且对10+1=11,再将11写入内存中。

第二轮 还是A的时间片,线程A读到内存中的11到寄存器中,这时候线程A的时间片到了 ,线程A将上下文数据保存到exc寄存器中(保存着11)并且将调度切换到B。

线程B读取内存中的11到寄存器中,对11+1=12,在写入内存。线程B的时间片比较充足,一直将变量从11加到100,此时线程B时间片结束,切换到线程A。

A将在exc寄存器中保存的11加载到CPU寄存器,然后+1=12,再把12拷贝会内存,覆盖已有的100.

此时就说明 +1 操作不是原子的(不是一步完成,会被调度原因中断)

而对于我们上述的例子,其实更为简单

假设目前票数是1,4个线程并行访问,进行if条件的判断,4个线程都判断成功,进入if函数的内部

这时候4个线程的访问由于延迟等,变成串行。

即线程A对变量1进行-- ,将1减到 0

线程B 在上一步已经通过if条件的判断,这时就从内存中把0加载到内存,进行-- 减到-1 ,再拷贝到内存

线程C 和B类似,也不必进行判断,就将-1 再继续-- 内存中就是-2

这种并行判断 ,串行访问修改,就造成票数出现负数!

为了解决这种情况,必须对临界资源的访问加上限制,必须只能是一个执行流对其访问。

这就是互斥!目的就是为了保护从并并访问,变成串行访问。

互斥锁mutex

  • 大部分时候,线程拥有独立的栈区,这些变量不会受到其它线程的影响,是安全的。
  • 但是对于全局变量,是线程共享的,为了满足线程的交互。
  • 但是线对于共享资源(临界资源)的访问是不安全的,需要被保护!很多时候只允许临界资源被一个执行流访问。

为了解决抢票出现负数(临界资源的保护

  • 临界区需要有互斥行为(有且同时只有一个执行流访问临界区)
  • 如果线程不执行临界区的资源,那么必须允许其它线程进入。

就好比有一间自习室,这间自习室只允许一个人进入学习。

那么A进入后,就给自习室上.了一把锁,其它人见到锁后,就不能进入。

如果A离开自习室,就必须放回锁,允许其他人进入。

在Linux中的锁叫做互斥量


锁的接口

创建锁

锁的申请,可以是一把全局锁,也可以是局部锁。

       #include <pthread.h>int pthread_mutex_init(pthread_mutex_t *restrict mutex,pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

全局锁
pthread_mutex_t  mutex

初始化为PTHREAD_MUTEX_INITIALIZER

后续不需要对锁destroy

局部锁

int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);

参数说明:

int pthread_mutex_lock(pthread_mutex_t *mutex);
  • mutex为需要初始化的锁
  • attr是锁的属性,一般为NULL

成功返回0,失败返回-1


锁的销毁

int pthread_mutex_destroy(pthread_mutex_t *mutex);
  • 全局的锁不会要显示的去销毁
  • 销毁的锁,必须不能是一把未解除的锁
  • 已经销毁的锁,必须保证在后续不会再被上锁。


加锁和解锁

对临界区加锁,同样是可以全局加锁,也可以是由局部锁去加。

加锁后必须要解锁

int pthread_mutex_lock(pthread_mutex_t *mutex);

参数为要加的锁。

成功返回0,失败返回错误码

如果一个锁没有被使用,则会返回0,申请上锁成功。

如果调用时,锁已经被使用。如果有其它执行流,同时在申请这把锁,但没有竞争这把锁成功则可能会发生这几种可能

  • 线程被阻塞等待:线程会被挂起阻塞或被放到等待队列中,直到锁的解除。
  • 自旋:循环等待,一直检查锁的情况,避免寄存器的切换。
  • 失败返回:如果调用者设置了锁申请失败的方法。

解锁:
对一把锁的解锁

用法与pthread_mutex_lock一致

int pthread_mutex_ulock(pthread_mutex_t *mutex);

锁的使用,解决抢票的问题。

全局锁的使用

在if条件前加锁,保证一次只有一个执行流在判断

解锁, 完成票的--或者是在break结束前解锁

局部锁

为了能让调用任务能够获取锁和线程名

我们封装一个数据类型

创建一个局部锁并初始化,创建锁类型的对象。

通过封装Pthread实例化出一个线程

可以观察到,局部的锁和全局的锁效果一致,都能做到互斥的效果。


关于锁的细节点

  • 访问同一个临界资源的线程,都必须加同一把锁。
  • 加锁是把线程的并行访问,互斥为串行访问,效率自然会降低。所以我们要尽可能少的加锁

访问临界区的时候需要先加锁,锁必须让所有的线程都看到,那么锁必然也是临界资源。

那么锁也要被安全!所以锁必须是原子的。(一步汇编指令就可以完成)

一个线程申请到锁,可以被切换走吗?

  •  一个线程申请到锁,仍然可以被切换走。线程的加锁,解锁本质也是代码。线程在麻袋任意处,包括加锁的代码,都可以OS或调度机制被切换走。切换是为了公平的享有资源。
  • 然而线程一旦有了锁,就不担心在持有锁的期间被切换走,因为申请锁的过程是原子的,是一步完成的。要么成功获取锁继续执行,要么获取失败阻塞住。在持有锁期间,其它线程无法进入,因为要进入必须申请锁。

互斥锁的原理

 大部分体系结构都存在俩条汇编指令

exchange 和 swap

目的是交换内存和CPU寄存器的数据

所谓的锁,最简单的结构其实就是一个结构体,结构体有一个成员int mutex=1

每次有线程申请这个锁,把0与寄存器中的1交换。在把0换到内存中,如果寄存器中的值是0,这锁申请成功。因为每一步操作都是原子的,不管是在哪一步切换线程,都不影响锁的申请。

下面从汇编角度理解锁的底层原理

有一把锁,就是在内存中定义一个mutex=1的变量,在CPU中有一个%a的寄存器

如果线程A去申请锁

会执行 mov 0 到 %a的寄存器中

交换内存中的mutex的值和%a的值,这一步操作也是原子的

最后执行判断,如果%a寄存器中的内容是1就代表锁申请成功,否则阻塞等待

在锁申请任何一步线程被切换中,都不会影响锁的申请,因为线程会带着寄存器(寄存器保存上下文数据,包括%a的0和1)离开。

等到线程被切换回来时,会加载寄存器内容,把刚才保存的%a再放回CPU中。


可重入VS线程安全

  • 线程安全:多个线程并发同一份代码时,不会出现不同的结果。常见的多线程对全局变量访问,如果没有加锁,线程就是不安全的。
  • 重入:同一个函数被不同的执行流调用时,一个执行流还没有结束,其它执行流就进入。一个函数如果在重入的情况下不会出现任何问题,就是可重入。而不可重入通常指的是一个函数在执行过程中,如果被其他线程或中断打断,并在该线程返回后再次调用,可能会导致错误的结果或行为。

线程安全讨论的是线程的特点,可重入讨论的函数的可重入。

常见线程不安全的情况:

  • 不保护全局的变量
  • 调用不安全的函数
  • 调用指向静态的指针
  • 总结就是 多线程访问没有加锁的全局变量、函数

常见不可重入的情况:

  • 调用malloc/free malloc函数是用全局链表来管理堆的
  • 调用标准IO库,IO库大部分是全局的
  • 可重入函数调用数据结构

可重入与线程安全的联系

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

死锁

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

假设有俩个线程A 和线程B 线程A持有锁lock1 线程B持有锁lock2

线程A去申请lock2,申请不会成功线程A被阻塞挂起

线程B去申请lock1,申请不会成功,线程B被阻塞挂起

导致线程全部都被挂起,这就是死锁。

单执行流也会出现死锁吗?

是的。如果一个线程连续申请俩次同一个锁,并且都没有解锁。第一次申请锁是成功的,因为没有线程竞争。第二次申请锁会失败,被阻塞挂起。导致执行流陷入阻塞等待,永远不会被唤醒。


产生死锁的必要条件

  • 互斥条件:一个资源每次被一个执行流使用。
  • 请求与保持:一个执行流因请求资源阻塞时,对已经获得的资源保持不放。
  • 不剥夺条件:一个执行流在以获得资源时,在未完成资源时,不强制剥夺其它资源。
  • 循环等待:若干个执行流构成头尾相连的循环等待资源状态。

解释:
互斥条件指是线程加锁。就如上述例子中。线程A和线程B都加锁。

请求与保持是不释放自身的锁。线程A去申请lock2锁的时候,不会把自己的锁释放掉。

不剥夺是不会强制释放要申请的锁。线程A在申请lock2时,不会把lock2先释放再申请。

循环等待指若干线程互相申请锁。比如线程A申请lock2 线程B申请lock3 线程C申请lock1。


避免死锁的方法:


最根本的方法就是不加锁。

其次就是破坏锁四的必要条件的一个或者多个。

  • 比如加锁顺序一致

线程A申请lock1 ,在线程B未持有lock2时候,线程A申请lock2。

  • 避免加锁未释放的场景,先释放锁再去申请锁。
  • 除此之外,还有一些避免死锁的算法,比如死锁检测算法和银行家算法。

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

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

相关文章

在 Java 中,如何使用文件保存包含文字和数字的 Swing 表格?

要在Java中使用文件保存包含文字和数字的Swing表格&#xff0c;您可以按照以下步骤进行&#xff1a; 1. 首先&#xff0c;您需要创建一个Swing表格&#xff0c;并向其中添加包含文字和数字的数据。 2. 接下来&#xff0c;您可以使用Java中的文件操作类&#xff08;如FileWrit…

【Xposed插件】 核心破解 兼容 9.0 去除系统签名校验,直装修改APK,降级安装APP

文章转载于: https://blog.coderstory.cn/corepatch-p/ 核心破解 是一款基于xposed模块开发的小工具。 可以用来去除系统签名校验&#xff0c;直装修改APK&#xff0c;降级安装APP. 至于用来干啥的&#xff0c;大家都懂的。 链接&#xff1a;https://pan.baidu.com/s/1Etp8tdJY…

设计模式 -- 2:策略模式

目录 总结部分&#xff1a;策略模式的优点部分代码部分 总结部分&#xff1a; 策略模式和简单工厂模式很像 区别在于 简单工厂模式 需求的是由工程创造的类 去给客户直接答案 而策略模式在于 我有主体 一个主体 根据策略的不同来进行不同的计算 我的主体就负责收钱 然后调度相…

工业级芯片之三问:静电可靠性是匠芯创芯片设计端的重要指标

导语 芯片是电子产品的核心&#xff0c;为了满足不同应用领域的需求&#xff0c;芯片被分为不同的等级。其中&#xff0c;工业级芯片适用于工业自动化、控制系统和仪器仪表等领域&#xff0c;对芯片的可靠性和稳定性要求较高。这些芯片通常具有更宽的工作温度范围&#xff08;…

Linux VM虚拟环境 设置静态IP

目录 查看自己的网卡配置说明重启网卡实例测试配置情况测试网络 查看自己的网卡 ip a配置文件 vim /etc/sysconfig/network-scripts/ifcfg-ens32配置说明 [rootlinux-server ~]# cd /etc/sysconfig/network-scripts/ #网卡配置文件存放路径 [rootlinux-server network-scri…

java-房屋信息网系统-209-(代码+说明)

转载地址: http://www.3q2008.com/soft/search.asp?keyword房屋信息网系统 本课题所确立的框架主要分为&#xff1a; 1、房屋信息发布。 发布房屋的信息包括新房信息、买卖房信息、二手房信息以及出租房屋的信息等。 2、房屋信息交流区。 提供一个给用户自由交流的一个空间。…

基于R语言piecewiseSEM结构方程模型在生态环境领域技术教程

原文链接&#xff1a;基于R语言piecewiseSEM结构方程模型在生态环境领域技术应用https://mp.weixin.qq.com/s?__bizMzUzNTczMDMxMg&mid2247597092&idx7&sn176695e746eccff68e04edda6521f131&chksmfa823dc3cdf5b4d5b77181eb1bd9a2d659ff38e23c7ea78d33bc1cc7d0…

跨域问题经典解决方法

这里写目录标题 背景步骤m3u8文件是什么&#xff1f;&#xff1f;本地播放m3u8文件在浏览器上播放m3u8视频跨域问题什么是跨域问题&#xff1f;为什么有跨域问题&#xff1f;如何解决跨域问题&#xff1f;使用代理服务器CORS&#xff08;跨域资源共享&#xff09; 总结 背景 同…

42.坑王驾到第八期:uniCloud报错

uniCloud 报错 今天调用云函数来调试小程序的时候突然暴了一个奇葩错误&#xff0c;require(…).main is not a function。翻官方文档后发现&#xff0c;原来是这样&#xff1a;**如果你写的是云对象&#xff0c;入口文件应为 index.obj.js&#xff0c;如果你写的是云函数入口…

C# 文件拖入控件中,显示文件路径

1.设置所需拖入的控件&#xff08;以Textbox为列&#xff09;属性为&#xff1a; this.textBox1.AllowDrop true; //设置AllowDrop 属性为 true&#xff0c;使之支持拖拽&#xff0c;否则拖拽显示禁用状态 2.设置该控件的两个事件&#xff0c;分别为&#xff1a; ①DragEnt…

COSCUP 2024 正式启动议题征集,开源社专属邀请通道开启,欢迎报名参加!

COSCUP 是由台湾开放原始码社群联合推动的年度研讨会&#xff0c;起源于 2006 年&#xff0c;是台湾自由软体运动 (FOSSM) 重要的推动者之一。活动包括有讲座、摊位、社团同乐会等&#xff0c;除了邀请国际的重量级演讲者之外&#xff0c;台湾本土的自由软体推动者也经常在此发…

RAG一文读懂!概念、场景、优势、对比微调与项目代码示例

本文结合“基于 ERNIE SDKLangChain 搭建个人知识库”的代码示例&#xff0c;为您讲解 RAG 的相关概念。 01 概念 在2020年 Facebook AI Research(FAIR)团队发表一篇名为《Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks》的论文。这篇论文首次提出了 RA…