【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.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 对共享资源的访问。
相关文章:

【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 概念 死锁是指在⼀组进程中的各个进程均占有不会释放的资源…...

Spring Web开发(请求)获取JOSN对象| 获取数据(Header)
大家好,我叫小帅今天我们来继续Spring Boot的内容。 文章目录 1. 获取JSON对象2. 获取URL中参数PathVariable3.上传⽂件RequestPart3. 获取Cookie/Session3.1 获取和设置Cookie3.1.1传统获取Cookie3.1.2简洁获取Cookie 3. 2 获取和存储Session3.2.1获取Session&…...

用c语言完成俄罗斯方块小游戏
用c语言完成俄罗斯方块小游戏 这估计是你在编程学习过程中的第一个小游戏开发,怎么说呢,在这里只针对刚学程序设计的学生,就是说刚接触C语言没多久,有一点功底的学生看看,简陋的代码,简陋的实现࿰…...

SpringBoot整合Retry详细教程
问题背景 在现代的分布式系统中,服务间的调用往往需要处理各种网络异常、超时等问题。重试机制是一种常见的解决策略,它允许应用程序在网络故障或临时性错误后自动重新尝试失败的操作。Spring Boot 提供了灵活的方式来集成重试机制,这可以通过…...

JS API事件监听(绑定)
事件监听 语法 元素对象.addEventListener(事件监听,要执行的函数) 事件监听三要素 事件源:那个dom元素被事件触发了,要获取dom元素 事件类型:用说明方式触发,比如鼠标单击click、鼠标经过mouseover等 事件调用的函数&#x…...

ceph手动部署
ceph手动部署 一、 节点规划 主机名IP地址角色ceph01.example.com172.18.0.10/24mon、mgr、osd、mds、rgwceph02.example.com172.18.0.20/24mon、mgr、osd、mds、rgwceph03.example.com172.18.0.30/24mon、mgr、osd、mds、rgw 操作系统版本: Rocky Linux release …...

superset load_examples加载失败解决方法
如果在执行load_examples命令后,出现上方图片情况,或是相似报错(url error\connection error),大概率原因是python程序请求github数据,无法访问. 因此我们可以将数据下载在本地来解决. 1.下载zip压缩文件,存放到本地 官方示例地址:GitHub - apache-superset/examples-data …...

wareshark分析mysql协议的数据包
使用wareshark 分析mysql协议的数据包,是每个dba都应该掌握的技能,掌握以后,就可以通过tcpdump抓包分析,得到连接报错的信息了。 tcpdump抓包命令: tcpdump -nn -i bond0 dst 10.21.6.72 and port 4002 -w 1129_tcpdu…...
HarmonyOS4+NEXT星河版入门与项目实战(25)------UIAbility启动模式(文档编辑案例)
文章目录 1、启动模式2、Specified启动模式实现步骤3、文档编辑案例1、文件创建2代码实现3、Statge 创建4、添加配置1、启动模式 Singleton启动模式: 每个 UIAbility 只存在一个实例,是默认的启动模式,任务列表中只会存在一个相同的 UIAbilityStandard启动模式: 每次启动 U…...

webpack 项目访问静态资源
使用 webpack dev serve 启动 react 项目后,发现无法使用 http://localhost:8080/1.png 访问到项目的 /static 目录下的 1.png 文件。我的 webpack-dev.js 配置如下: const webpack require(webpack) const webpackMerge require(webpack-merge) cons…...
UNION和UNION ALL区别
文章目录 结果集的处理方式:对重复记录的处理:排序处理:执行效率: UNION和UNION ALL的主要区别在于结果集的处理方式、对重复记录的处理、排序处理以及执行效率。 结果集的处理方式: UNION…...

Rook入门:打造云原生Ceph存储的全面学习路径(下)
文章目录 六.Rook部署云原生CephFS文件系统6.1 部署cephfs storageclass6.2 创建容器所需cephfs文件系统6.3创建容器pod使用rook-cephfs提供pvc6.4 查看pod是否使用rook-cephfs 七.Ceph Dashboard界面7.1 启用dashboard开关7.2 ceph-dashboard配置外部访问7.3 Dashboard web ad…...

RabbitMQ消息可靠性保证机制6--可靠性分析
在使用消息中间件的过程中,难免会出现消息错误或者消息丢失等异常情况。这个时候就需要有一个良好的机制来跟踪记录消息的过程(轨迹溯源),帮助我们排查问题。 在RabbitMQ中可以使用Firehose实现消息的跟踪,Firehose可…...
k8s容器存储接口 CSI 相关知识
容器存储接口 CSI 相关知识 参考: https://blog.csdn.net/lovely_nn/article/details/122880876 https://developer.aliyun.com/article/783464 https://www.cnblogs.com/varden/p/15139819.html存储商需实现 CSI 插件的 NodeGetVolumeStats 接口,Kube…...

jmeter基础_打开1个jmeter脚本(.jmx文件)
课程大纲 方法1.菜单栏“打开” 菜单栏“文件” - “打开” (或快捷键,mac为“⌘ O”),打开文件选择窗口 - 选择脚本文件,点击“open”,即可打开脚本。 方法2.工具栏“打开”图标 工具栏点击“打开”图标&…...

Linux---对时/定时服务
文章目录 目录 文章目录 前言 一.对时服务 服务端配置 客户端配置 二.定时服务 单次定时任务 循环定时任务 前言 在当今信息化高速发展的时代,时间的准确性和任务的定时执行对于各种系统和服务来说至关重要。Linux操作系统,凭借其强大的功能和灵活的…...

Agent
Agent核心 1、自主性 2、交互性 3、适应性 4、目的性 ReAct Reasoning and Acting范式 模型的推理过程分为 推理 Reason 和行动 Action 两个步骤,交替执行,直至获得最终结果。 推理 Reason 生成分析步骤,解释当前任务的上下文或状态…...
Oracle 数据库执行增删改查命令的原理与过程
摘要: 本文深入探讨当向 Oracle 数据库发送一个增删改查(CRUD)命令时,数据库内部的执行机制与详细过程。从用户发起命令开始,逐步剖析命令在 Oracle 数据库体系结构各组件中的流转、解析、优化以及执行路径,…...
HBase难点
查询优化 一次Scan会返回大量数据,客户端向HBase发送一次Scan请求,实际上并不会将所有数据加载到本地,而是通过多次RPC请求进行加载,防止客户端OOM。禁止缓存优化:批量读取数据时会全表扫描一次业务表,这种…...

Y20030023 PHP+thinkphp+MYSQL+LW+基于PHP的健身房管理系统的设计与实现 源代码 配置 初稿
基于PHP的健身房管理系统 1.项目摘要2. 系统开发的背景及意义3.项目功能4.界面展示5.源码获取 1.项目摘要 近年来,随着社会发展和科技进步,人们越来越重视健康养生并关注电子商务对日常交流方式的影响。随着健身行业消费人群的增加,竞争变得…...

接口测试中缓存处理策略
在接口测试中,缓存处理策略是一个关键环节,直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性,避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明: 一、缓存处理的核…...

XCTF-web-easyupload
试了试php,php7,pht,phtml等,都没有用 尝试.user.ini 抓包修改将.user.ini修改为jpg图片 在上传一个123.jpg 用蚁剑连接,得到flag...
Java 8 Stream API 入门到实践详解
一、告别 for 循环! 传统痛点: Java 8 之前,集合操作离不开冗长的 for 循环和匿名类。例如,过滤列表中的偶数: List<Integer> list Arrays.asList(1, 2, 3, 4, 5); List<Integer> evens new ArrayList…...

前端导出带有合并单元格的列表
// 导出async function exportExcel(fileName "共识调整.xlsx") {// 所有数据const exportData await getAllMainData();// 表头内容let fitstTitleList [];const secondTitleList [];allColumns.value.forEach(column > {if (!column.children) {fitstTitleL…...
在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module
1、为什么要修改 CONNECT 报文? 多租户隔离:自动为接入设备追加租户前缀,后端按 ClientID 拆分队列。零代码鉴权:将入站用户名替换为 OAuth Access-Token,后端 Broker 统一校验。灰度发布:根据 IP/地理位写…...
将对透视变换后的图像使用Otsu进行阈值化,来分离黑色和白色像素。这句话中的Otsu是什么意思?
Otsu 是一种自动阈值化方法,用于将图像分割为前景和背景。它通过最小化图像的类内方差或等价地最大化类间方差来选择最佳阈值。这种方法特别适用于图像的二值化处理,能够自动确定一个阈值,将图像中的像素分为黑色和白色两类。 Otsu 方法的原…...

2025 后端自学UNIAPP【项目实战:旅游项目】6、我的收藏页面
代码框架视图 1、先添加一个获取收藏景点的列表请求 【在文件my_api.js文件中添加】 // 引入公共的请求封装 import http from ./my_http.js// 登录接口(适配服务端返回 Token) export const login async (code, avatar) > {const res await http…...
Python如何给视频添加音频和字幕
在Python中,给视频添加音频和字幕可以使用电影文件处理库MoviePy和字幕处理库Subtitles。下面将详细介绍如何使用这些库来实现视频的音频和字幕添加,包括必要的代码示例和详细解释。 环境准备 在开始之前,需要安装以下Python库:…...
汇编常见指令
汇编常见指令 一、数据传送指令 指令功能示例说明MOV数据传送MOV EAX, 10将立即数 10 送入 EAXMOV [EBX], EAX将 EAX 值存入 EBX 指向的内存LEA加载有效地址LEA EAX, [EBX4]将 EBX4 的地址存入 EAX(不访问内存)XCHG交换数据XCHG EAX, EBX交换 EAX 和 EB…...
#Uniapp篇:chrome调试unapp适配
chrome调试设备----使用Android模拟机开发调试移动端页面 Chrome://inspect/#devices MuMu模拟器Edge浏览器:Android原生APP嵌入的H5页面元素定位 chrome://inspect/#devices uniapp单位适配 根路径下 postcss.config.js 需要装这些插件 “postcss”: “^8.5.…...