【Linux】死锁、读写锁、自旋锁

文章目录

  • 1. 死锁
    • 1.1 概念
    • 1.2 死锁形成的四个必要条件
    • 1.3 避免死锁
  • 2. 读者写者问题与读写锁
    • 2.1 读者写者问题
    • 2.2 读写锁的使用
    • 2.3 读写策略
  • 3. 自旋锁
    • 3.1 概念
    • 3.2 原理
    • 3.3 自旋锁的使用
    • 3.4 优点与缺点

在这里插入图片描述

1. 死锁

1.1 概念

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

在这里插入图片描述
所以,可能会造成死锁的局面
在这里插入图片描述

1.2 死锁形成的四个必要条件

  1. 互斥条件:⼀个资源每次只能被⼀个执行流使用
  2. 请求与保持条件:⼀个执行流因请求资源而阻塞时,对已获得的资源保持不放在这里插入图片描述
  3. 不剥夺条件:⼀个执行流已获得的资源,在未使用完之前,不能强行剥夺
    在这里插入图片描述
  4. 循环等待条件:若干执行流之间形成⼀种头尾相接的循环等待资源的关系
    在这里插入图片描述

1.3 避免死锁

方式:破坏死锁的四个必要条件。

互斥条件:

  • 破坏难度:由于互斥性是资源访问的基本特性,因此很难或不应该被破坏。

请求和保持条件:

  • 破坏策略:可以采用静态分配策略,即进程在运行前一次性申请完它所需要的全部资源,在资源未满足前,它不启动。这样就不会出现占有资源又等待其他资源的情况,从而破坏请求和保持条件。

不剥夺条件

  • 破坏策略:可以采用剥夺式调度策略,即当一个进程申请新资源而得不到满足时,可以释放它所占有的部分资源,以便其他进程使用,从而破坏不剥夺条件。

循环等待条件

  • 破坏策略:可以采用顺序资源分配法,即首先给系统中的资源编号,规定每个进程必须按编号递增的顺序请求资源,只能申请编号比之前大的资源。这样可以避免形成循环等待链,从而破坏循环等待条件。

例如:破环请求与保持条件,使资源⼀次性分配。

lock函数可确保所传递的锁对象全部获取成功,本质就是先申请一把锁,在申请的锁种再申请提供的锁对象,因为申请一把锁的操作是原子的。

在这里插入图片描述

#include <iostream>
#include <mutex>
#include <unistd.h>
// 定义两个共享资源(整数变量)和两个互斥锁
int shared_resource1 = 0;
int shared_resource2 = 0;
std::mutex mtx1, mtx2;
// ⼀个函数,同时访问两个共享资源
void access_shared_resources()
{std::unique_lock<std::mutex> lock1(mtx1, std::defer_lock);std::unique_lock<std::mutex> lock2(mtx2, std::defer_lock);// // 使⽤ std::lock 同时锁定两个互斥锁std::lock(lock1, lock2);// 现在两个互斥锁都已锁定,可以安全地访问共享资源int cnt = 10000;while (cnt){++shared_resource1;++shared_resource2;cnt--;} // 当离开 access_shared_resources 的作⽤域时,lock1 和 lock2 的析构函数会被⾃动调⽤// 这会导致它们各⾃的互斥量被⾃动解锁
} 

2. 读者写者问题与读写锁

2.1 读者写者问题

读者写者问题其实与生产者消费者问题类似,都是多线程之间互相同步的一种策略。

例如我们在写博客的时候,在我写的时候你是看不到的,直到我发布出去,你才能看到;与此同时在你看的时候,可能还有很多人都在看。

读者写者问题也应该遵循“321"原则:3种关系,2种角色,1个交易场所。

三种关系如下:

  • 写者与写者之间互斥,即一个写者在修改数据时,其他写者不能访问。
  • 读者与写者之间互斥&&同步,即不能同时有一个线程在读,而另一个线程在写。
    • 当一个读者申请进行读操作时,如果已有写者在访问共享资源,则该读者必须等到没有写者访问后才能开始读操作。
    • 当一个写者申请进行写操作时,如果已有读者正在读取数据,写者必须等待所有读者完成读取后才能开始写操作。
  • 读者之间可以并发(即没关系),即可以有一个或多个读者在读。

读者写者 vs 生成者消费者

二者最大的区别就是:消费者会“取走”共享资源,而读者不会。

伪代码理解读者写者的逻辑:

在这里插入图片描述

2.2 读写锁的使用

初始化:

  • int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthread_rwlockattr_t *restrict attr);
  • pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;

销毁:

  • int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

加锁:

  • 读者加锁int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
  • 写者加锁int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

解锁:

  • int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
#include <iostream>
#include <unistd.h>#include <pthread.h>
#include <sys/types.h>pthread_rwlock_t rwlock; // 读写锁
static int g_data = 0; // 共享资源pthread_mutex_t lockScreen;void *Reader(void *args)
{int id = *(int *)args;while (true){pthread_rwlock_rdlock(&rwlock); // 读者加锁pthread_mutex_lock(&lockScreen);std::cout << "读者-" << id << " 正在读取数据, 数据是: " << g_data << std::endl;pthread_mutex_unlock(&lockScreen);sleep(2);pthread_rwlock_unlock(&rwlock); // 读者解锁sleep(1);}delete (int *)args;return nullptr;
}void *Writer(void *args)
{int id = *(int *)args;while (true){pthread_rwlock_wrlock(&rwlock); // 写者加锁g_data += 2;                    // 修改共享数据pthread_mutex_lock(&lockScreen);std::cout << "写者- " << id << " 正在写入. 新的数据是: " << g_data << std::endl;pthread_mutex_unlock(&lockScreen);sleep(1);pthread_rwlock_unlock(&rwlock); // 解锁sleep(1);}delete (int *)args;return NULL;
}int main()
{pthread_rwlock_init(&rwlock, nullptr); // 初始化读写锁pthread_mutex_init(&lockScreen,nullptr);const int reader_num = 5; // 读者数量const int writer_num = 10;  // 写者数量const int total = reader_num + writer_num;pthread_t threads[total];for (int i = 0; i < reader_num; i++){int *id = new int(i);pthread_create(&threads[i], 0, Reader, id);}for (int i = reader_num; i < total; i++){int *id = new int(i - reader_num);pthread_create(&threads[i], 0, Writer, id);}for (int i = 0; i < total; i++){pthread_join(threads[i], nullptr);}return 0;
}

在这里插入图片描述

2.3 读写策略

  • 读者优先
    在这种策略中, 系统会尽可能多地允许多个读者同时访问资源(比如共享文件或数据) , 而不会优先考虑写者。 这意味着当有读者正在读取时, 新到达的读者会立即被允许进入读取区, 而写者则会被阻塞, 直到所有读者都离开读取区。 读者优先策略可能会导致写者饥饿(即写者长时间无法获得写入权限), 特别是当读者频繁到达时。
  • 写者优先
    在这种策略中, 系统会优先考虑写者。 当写者请求写入权限时, 系统会尽快地让写者进入写入区, 即使此时有读者正在读取。 这通常意味着一旦有写者到达, 所有后续的读者都会被阻塞, 直到写者完成写入并离开写入区。 写者优先策略可以减少写者等待的时间, 但可能会导致读者饥饿(即读者长时间无法获得读取权限) , 特别是当写者频繁到达时。

选择合适的策略时,需要根据具体的应用场景和需求进行权衡。例如,在需要频繁读取而写入较少的应用中,读者优先策略可能更为合适;而在需要频繁写入的应用中,写者优先策略可能更为合适。

3. 自旋锁

3.1 概念

在我们之前讲的信号量或互斥锁,都有一个特点:申请锁失败,申请线程都要阻塞挂起等待。

但是,当一个线程在临界区内执行的时长非常短时,那么等待线程阻塞、挂起、唤醒的代价是比较大的。所以有一种锁在申请临界区的时候,可以不阻塞等待,它会持续自旋(即在一个循环中不断检查锁是否可用),这种状态的锁我们称之为自旋锁

这种机制减少了线程切换的开销, 适用于短时间内锁的竞争情况

3.2 原理

自旋锁通常使用一个共享的标志位(如一个布尔值)来表示锁的状态。 当标志位为true 时,表示锁已被某个线程占用;当标志位为 false 时,表示锁可用。 当一个线程尝试获取自旋锁时,它会不断检查标志位:

  • 如果标志位为 false,表示锁可用, 线程将设置标志位为 true, 表示自己占用了锁, 并进入临界区。
  • 如果标志位为 true(即锁已被其他线程占用),线程会在一个循环中不断自旋等待, 直到锁被释放

上面检测标志位的操作一定是原子性的。

伪代码理解原理:
在这里插入图片描述

3.3 自旋锁的使用

Linux 提供的自旋锁系统调用

  • 初始化:
    int pthread_spin_init(pthread_spinlock_t *lock, int pshared);

在这里插入图片描述

  • 销毁:
    int pthread_spin_destroy(pthread_spinlock_t *lock);

  • 加锁
    int pthread_spin_lock(pthread_spinlock_t *lock);
    int pthread_spin_trylock(pthread_spinlock_t *lock); 申请锁失败就返回,就可以使用适当的退避策略了。

  • 解锁:
    int pthread_spin_unlock(pthread_spinlock_t *lock);

使用:

#include <iostream>
#include <unistd.h>
#include <pthread.h>pthread_spinlock_t spinlock;    //自旋锁static int g_ticket = 5000;void* func(void* args)
{char* name = (char*)args;while(true){pthread_spin_lock(&spinlock);if(g_ticket > 0){usleep(1000);printf("%s sells ticket:%d\n", name, g_ticket);g_ticket--;pthread_spin_unlock(&spinlock);}else{pthread_spin_unlock(&spinlock);break;}}return nullptr;
}int main()
{pthread_spin_init(&spinlock,PTHREAD_PROCESS_PRIVATE);	//初始化pthread_t t1,t2,t3,t4,t5;pthread_create(&t1,nullptr,func,(void*)"thread-1");pthread_create(&t2,nullptr,func,(void*)"thread-2");pthread_create(&t3,nullptr,func,(void*)"thread-3");pthread_create(&t4,nullptr,func,(void*)"thread-4");pthread_create(&t5,nullptr,func,(void*)"thread-5");pthread_join(t1,nullptr);pthread_join(t2,nullptr);pthread_join(t3,nullptr);pthread_join(t4,nullptr);pthread_join(t5,nullptr);pthread_spin_destroy(&spinlock);	//销毁return 0;
}

3.4 优点与缺点

优点

  • 低延迟: 自旋锁适用于短时间内的锁竞争情况, 因为它不会让线程进入休眠状态, 从而避免了线程切换的开销, 提高了锁操作的效率。
  • 减少系统调度开销: 等待锁的线程不会被阻塞, 不需要上下文切换, 从而减少了系统调度的开销。

缺点

  • CPU 资源浪费: 如果锁的持有时间较长,等待获取锁的线程会一直循环等待,导致 CPU 资源的浪费。
  • 可能引起活锁: 当多个线程同时自旋等待同一个锁时, 如果没有适当的退避策略, 可能会导致所有线程都在不断检查锁状态而无法进入临界区, 形成活锁

使用场景

  • 短暂等待的情况: 适用于锁被占用时间很短的场景, 如多线程对共享数据进行简单的读写操作。
  • 多线程锁使用: 通常用于系统底层, 同步多个 CPU 对共享资源的访问。

在这里插入图片描述

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

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

相关文章

Vue3之弹窗

文章目录 第一步、引入JS第二步、弹框 在前端开发语言Vue3&#xff0c;在管理端如何进行弹窗&#xff1f;下面根据API实现效果。 Element API文档&#xff1a; Element-plus文档 搭建环境可参考博客【 初探Vue3环境搭建与nvm使用】 第一步、引入JS <script lang"ts&…

el-tree的使用及控制全选、反选、获取选中

el-tree的使用及控制全选、反选、获取选中 组件使用获取选中的id全选实现反选实现全部代码 组件使用 引入组件&#xff0c;可以参考官网组件引入参考官网示例写好基础数据结构&#xff0c;不知道怎么转换树形机构的看文章&#xff1a;一维数组转树形 <template><el-…

MySQL - 性能优化

使用 Explain 进行分析 Explain 用来分析 SELECT 查询语句&#xff0c;开发人员可以通过分析 Explain 结果来优化查询语句。 比较重要的字段有: select_type : 查询类型&#xff0c;有简单查询、联合查询、子查询等 key : 使用的索引 rows : 扫描的行数 type &#xff1a;…

2、Three.js初步认识场景Scene、相机Camera、渲染器Renderer三要素

三要素之间关系&#xff1a; 有了虚拟场景Scene&#xff0c;相机录像Camera&#xff0c;在相机小屏幕上看到的Renderer Scene当前空间 Mesh人在场景 Camera相机录像 Renderer显示器上 首先先描述下Scene&#xff1a; 这个场景为三要素之一&#xff0c;一切需要展示的东西都需…

【docker】容器卷综合讲解,以及go实现的企业案例

容器卷&#xff08;Volumes&#xff09;基础讲解&#xff1a; 容器概念 容器卷&#xff08;Volumes&#xff09;是 Docker 提供的一种持久化存储机制&#xff0c;允许容器持久化数据&#xff0c;即使容器被删除或重新创建&#xff0c;数据仍然可以保留。 卷是独立于容器的&…

cin/cout的性能优化和缓冲区同步问题

目录 背景导入 问题 1.1ios::sync_with_stdio(false) 1.2为什么要解除C/C IO流同步? 1.3使用场景 2.1cin和cout的绑定关系 2.2为什么要解除绑定关系? 2.3注意事项 背景导入 大家可以先看一下这段背景知识;后面我会谈谈自己的理解; 1.在C中&#xff0c;标准输⼊输出流…

node.js基础学习-url模块-url地址处理(二)

前言 前面我们创建了一个HTTP服务器&#xff0c;如果只是简单的http://localhost:3000/about这种链接我们是可以处理的&#xff0c;但是实际运用中一般链接都会带参数&#xff0c;这样的话如果我们只是简单的判断链接来分配数据&#xff0c;就会报404找不到链接。为了解决这个问…

基于springboot中小型制造企业质量管理系统源码和论文

信息数据从传统到当代&#xff0c;是一直在变革当中&#xff0c;突如其来的互联网让传统的信息管理看到了革命性的曙光&#xff0c;因为传统信息管理从时效性&#xff0c;还是安全性&#xff0c;还是可操作性等各个方面来讲&#xff0c;遇到了互联网时代才发现能补上自古以来的…

服务器密码错误被锁定怎么解决?

当服务器密码错误多次导致账号被锁定时&#xff0c;解决方法需要根据服务器的操作系统&#xff08;如 Linux 或 Windows &#xff09;和具体服务器环境来处理。以下是常见的解决办法&#xff1a; 一、Linux 服务器被锁定的解决方法 1. 使用其他用户账号登录 如果有其他未被…

Java基础——(四)继承

1. 类、超类和子类 在Java中&#xff0c;通过关键字extends表示继承。extends表明正在构造的新类派生与一个已存在的类&#xff0c;已存在的类称为超类&#xff08;superclass&#xff09;、基类&#xff08;base class&#xff09;或父类&#xff08;parent class&#xff09…

Python语法基础(四)

&#x1f308;个人主页&#xff1a;羽晨同学 &#x1f4ab;个人格言:“成为自己未来的主人~” 高阶函数之map 高阶函数就是说&#xff0c;A函数作为B函数的参数&#xff0c;B函数就是高阶函数 map&#xff1a;映射 map(func,iterable) 这个是map的基本语法&#xff0c;…

《datawhale2411组队学习 模型压缩技术7:NNI剪枝》

文章目录 一、NNI简介二、 NNI剪枝快速入门2.1 加载并训练模型2.2 模型剪枝2.3 模型加速&#xff08;剪枝永久化&#xff09;2.4 微调压缩模型2.5 Slim Pruner测试 三、 使用NNI3.0进行Bert压缩&#xff08;剪枝、蒸馏)3.1 数据预处理3.2 训练模型3.3 设置模型蒸馏函数3.4 修剪…

day22:lamp项目部署

一&#xff0c;lamp概述 lamp概述 LAMP 是一组开源软件的缩写&#xff0c;用于搭建动态网站或Web应用程序的基础环境。LAMP 代表了四个主要的组成部分&#xff1a; Linux&#xff1a;操作系统&#xff0c;LAMP 环境的基础。通常使用的是 Linux 发行版&#xff0c;如 CentOS、…

【提高篇】3.6 GPIO(六,寄存器介绍,下)

目录 2.3 输出速度寄存器OSPEEDR(GPIOx_OSPEEDR) (x = A..I) 2.4 上拉/下拉寄存器 (GPIOx_PUPDR) (x = A..I) 2.5 输入数据寄存器(IDR) 2.6 输出数据寄存器(ODR) 2.7 置位/复位寄存器(BSRR) 2.8 BSRR与ODR寄存器的区别 2.3 输出速度寄存器OSPEEDR(GPIOx_OSPEEDR) (…

Java中的“抽象类“与“接口“之间的关系

在Java中&#xff0c;抽象类和接口都是用于实现抽象的机制&#xff0c;但它们在设计目的、使用方式以及功能上有一些不同。下面我将通过文字和代码示例来详细讲解它们之间的关系。 1. 抽象类&#xff08;Abstract Class&#xff09; 特点&#xff1a; 抽象类使用abstract关键字…

ROS2-参数服务器

在 ROS 2 中&#xff0c;节点之间可以通过参数服务器共享和获取参数。这意味着一个节点可以声明一个参数&#xff0c;而其他节点可以读取或修改这个参数。这是通过 ROS 2 的参数系统实现的&#xff0c;它允许节点在参数服务器上声明、设置和获取参数 。 0. 背景 系统有多个 RO…

CSS3网站

&#xff08;看不懂英文的记得点击右上角Change language更改语言&#xff09;&#xff1a; https://developer.mozilla.org/en-US/docs/Learn/CSS/Introduction_to_CSS/Selectors CSS选择器 https://developer.mozilla.org/en-US/docs/Web/CSS/color_value 颜色值 https://ser…

js进阶-中括弧运算

一、题目 下面代码的输出是什么 console.log([[]][[]] [[]]); 二、变量类型转换 1.对于数字运算[负号-]、[减号-]、[乘号*]、[除号/]、[自加号]等&#xff0c;其后的变量都会被尝试转换为数字&#xff1b; 如果其后变量是基础数据类型&#xff0c;那么按基础数据类型转换…

【IMF靶场渗透】

文章目录 一、基础信息 二、信息收集 三、flag1 四、flag2 五、flag3 六、flag4 七、flag5 八、flag6 一、基础信息 Kali IP&#xff1a;192.168.20.146 靶机IP&#xff1a;192.168.20.147 二、信息收集 Nmap -sP 192.168.20.0/24 Arp-scan -l nmap -sS -sV -p- -…

记一次腾讯云海外服务器http能正常访问https访问拒绝问题处理过程

最近双十一, 购了一台腾讯云的海外服务器&#xff0c; 开通后就是一堆的服务器软件安装数据上传和配置&#xff0c;没想到&#xff0c;等待配置完成后才发现https无法正常访问&#xff0c;于是开启了自查。 1. 检查nginx软件的ssl配置 nginx http https配置参考 server {l…