当前位置: 首页 > news >正文

【Linux】旋转锁 | 读写锁

在之前的线程学习中,用到的锁都是挂起等待锁,如果申请不到锁,那就会在锁中等待;

自旋锁则不大相似

文章目录

  • 1.自旋锁
    • 1.1 概念
    • 1.2 接口
      • 1.2.1 pthread_spin_init/destroy
      • 1.2.2 pthread_spin_lock
      • 1.2.3 pthread_spin_unlock
  • 2.读写锁
    • 2.1 读者写者的关系
    • 2.2 接口
      • 2.2.1 init/destroy
      • 2.2.2 读者加锁
      • 2.2.3 写者加锁
      • 2.2.4 设置锁的属性
    • 2.3 代码

1.自旋锁

1.1 概念

自旋锁是一个轮询检测锁,其检测机制并不是挂起等待,而是不断的询问锁有没有空闲;类似于一个while(1)循环的trylock()

由于其需要不断的轮询检测,所以会占用一定的CPU资源;如果线程较多,就容易给cpu造成负荷。

但是自旋锁无须唤醒挂起等待状态的线程,其消耗较小。

总结一下:

  • 自旋锁适合竞争不激烈,且临界区较小(呆的时间短)的情况
  • 自旋锁不适合大量线程,临界区长的情况

自旋锁的优缺点反过来,便是挂起等待锁的优缺点了。我们要根据不同场景,正确选择锁的类型

1.2 接口

相关接口和mutex都是很相似的,这里就不演示使用的效果了

1.2.1 pthread_spin_init/destroy

#include <pthread.h>int pthread_spin_destroy(pthread_spinlock_t *lock);
int pthread_spin_init(pthread_spinlock_t *lock, int pshared);

1.2.2 pthread_spin_lock

自旋锁同样有trylock接口,用于判断锁是否就绪

#include <pthread.h>
int pthread_spin_lock(pthread_spinlock_t *lock);
int pthread_spin_trylock(pthread_spinlock_t *lock);

1.2.3 pthread_spin_unlock

#include <pthread.h>
int pthread_spin_unlock(pthread_spinlock_t *lock);

2.读写锁

有的时候,我们会有一份config配置文件,这个配置文件会有非常多的线程进行读取,但是很少进行修改和写入。

此时我们如果对配置文件的读取进行加锁,就容易导致效率问题,众多线程不断被阻塞,产生性能损失。

此时,就可以用一个专门的读写锁,对读者和写者加不同的锁,在提升读取性能的同时,保证写入不冲突

2.1 读者写者的关系

写着和写着之间不用多说,肯定是互斥关系;

读者和写者之间也是互斥关系,在写入的时候,不能进行读取,否则容易出现二义性问题;

  • 写者写入了一个a,线程甲来读取,得到的结果是a
  • 写者继续写入了b,线程乙来读取,得到的结果是ab

这是因为写者的写入还没有完成,导致甲乙读者会获取到完全不同的结果,这是不对的;

读者和读者之间没有关系,因为读者并不会修改数据,也不会取走数据,其存在对临界资源没有影响。


2.2 接口

2.2.1 init/destroy

读写锁只需要初始一个锁就行了,无须对读者写者初始化两个不同的锁

#include <pthread.h>int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthread_rwlockattr_t *restrict attr);

2.2.2 读者加锁

读写锁的读者锁/写者锁是分开的,我们要针对不同的线程调用不同的锁

#include <pthread.h>int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);

但是解锁的接口是一样的

#include <pthread.h>
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

2.2.3 写者加锁

#include <pthread.h>
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

2.2.4 设置锁的属性

参考 PTHREAD_RWLOCKATTR_SETKIND_NP - Linux手册页

读写锁可以允许我们设置是读者优先还是写者优先。如果采用默认的属性,可能会出现读者一直在读,写者没有办法写入的情况(打印错位是正常情况)

[muxue@bt-7274:~/git/linux/code/23-01-20 rwlock]$ ./test
reader [140484801697536]
reader [140484801697536] 0
reader [140484793304832]
reader [reader [140484784912128140484793304832]
reader [140484784912128] 0
] 0
reader [140484759734016]
reader [140484759734016] 0
reader [140484776519424]

此时就出现了写者饥饿问题,写者无法访问临界资源,饿死了😂

我们可以根据自己的需求进行设置读写锁的属性

int pthread_rwlockattr_setkind_np(pthread_rwlockattr_t *attr, int pref); 
/* 
pref 共有 3 种选择
PTHREAD_RWLOCK_PREFER_READER_NP (默认设置) 读者优先,可能会导致写者饥饿情况
PTHREAD_RWLOCK_PREFER_WRITER_NP 写者优先,目前有 BUG,导致表现行为和 
PTHREAD_RWLOCK_PREFER_READER_NP 一致
PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP 写者优先,但写者不能递归加锁,避免死锁
*/

下面是一个示例

pthread_rwlock_t rwlock;//锁
pthread_rwlockattr_t attr;//属性
pthread_rwlockattr_init(&attr);//初始化属性
pthread_rwlockattr_setkind_np(&attr, PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP);//设置锁的属性为写者优先
pthread_rwlock_init(&rwlock, &attr);//初始化并设置读写锁的属性

2.3 代码

#include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;volatile int board = 0;//临界资源pthread_rwlock_t rw;//全局读写锁void *reader(void* args)
{const char *name = static_cast<const char *>(args);cout << "reader ["<<pthread_self() <<"]"<< endl;//sleep(1);while(true){pthread_rwlock_rdlock(&rw);cout << "reader ["<<pthread_self() <<"] " << board << endl;pthread_rwlock_unlock(&rw);usleep(110);}
}void *writer(void *args)
{const char *name = static_cast<const char *>(args);//sleep(1);while(true){pthread_rwlock_wrlock(&rw);board++;cout << "writer [" << pthread_self() <<"]"<< endl;pthread_rwlock_unlock(&rw);usleep(100);}
}int main()
{pthread_rwlock_init(&rw, nullptr);pthread_t r1,r2,r3,r4,r5,r6, w1,w2;pthread_create(&r1, nullptr, reader, (void*)"reader");pthread_create(&r2, nullptr, reader, (void*)"reader");pthread_create(&r3, nullptr, reader, (void*)"reader");pthread_create(&r4, nullptr, reader, (void*)"reader");pthread_create(&r5, nullptr, reader, (void*)"reader");pthread_create(&r6, nullptr, reader, (void*)"reader");pthread_create(&w1, nullptr, writer, (void*)"writer");pthread_create(&w2, nullptr, writer, (void*)"writer");pthread_join(r1, nullptr);pthread_join(r2, nullptr);pthread_join(r3, nullptr);pthread_join(r4, nullptr);pthread_join(r5, nullptr);pthread_join(r6, nullptr);pthread_join(w1, nullptr);pthread_join(w2, nullptr);pthread_rwlock_destroy(&rw);return 0;
}

运行结果如下

reader [140067523458816] 7086
reader [140067548636928] 7086
writer [140067515066112]
writer [140067506673408]
reader [140067540244224] 7088
reader [140067565422336] 7088
reader [140067557029632] 7088
reader [140067531851520] 7088
reader [140067523458816] 7088
writer [140067515066112]
writer [140067506673408]
reader [140067548636928] 7090
writer [140067515066112]
reader [140067523458816] 7091
writer [140067506673408]
reader [140067548636928] 7092
reader [140067557029632] 7092

可以看到读者的线程较多,且能够正确读取数据。

如果在读者的whiile中加上sleep(10)

void *reader(void* args)
{const char *name = static_cast<const char *>(args);cout << "reader ["<<pthread_self() <<"]"<< endl;while(true){pthread_rwlock_rdlock(&rw);cout << "reader ["<<pthread_self() <<"] " << board << endl;sleep(10);//睡pthread_rwlock_unlock(&rw);usleep(110);//避免出现只有一个线程工作}
}

能够看到多个读者之间不冲突,不会出现读者A申请锁后,读者B就无法访问临界区的情况。如果是互斥锁,读者A申请之后进入休眠,B就无法申请该锁。

image-20230120163852340

相关文章:

【Linux】旋转锁 | 读写锁

在之前的线程学习中&#xff0c;用到的锁都是挂起等待锁&#xff0c;如果申请不到锁&#xff0c;那就会在锁中等待&#xff1b; 自旋锁则不大相似 文章目录1.自旋锁1.1 概念1.2 接口1.2.1 pthread_spin_init/destroy1.2.2 pthread_spin_lock1.2.3 pthread_spin_unlock2.读写锁…...

EasyExcell导出excel添加水印

EasyExcell导出excel添加水印1、添加easyExcel相关依赖2、准备基础工具类3、创建水印handler类4、创建单元测试类WriteTest.class5、测试结果1、添加easyExcel相关依赖 <dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId&…...

SpringCloud:Nacos配置管理

Nacos除了可以做注册中心&#xff0c;同样可以做配置管理来使用。 1.1.统一配置管理 当微服务部署的实例越来越多&#xff0c;达到数十、数百时&#xff0c;逐个修改微服务配置就会让人抓狂&#xff0c;而且很容易出错。我们需要一种统一配置管理方案&#xff0c;可以集中管理…...

正则表达式引擎NFA自动机的回溯解决方案总结

前几天线上一个项目监控信息突然报告异常&#xff0c;上到机器上后查看相关资源的使用情况&#xff0c;发现 CPU 利用率将近 100%。通过 Java 自带的线程 Dump 工具&#xff0c;我们导出了出问题的堆栈信息。 我们可以看到所有的堆栈都指向了一个名为 validateUrl 的方法&#…...

卷积神经网络之AlexNet

目录概述AlexNet特点激活函数sigmoid激活函数ReLu激活函数数据增强层叠池化局部相应归一化DropoutAlexnet网络结构网络结构分析AlexNet各层参数及其数量模型框架形状结构关于数据集训练学习keras代码示例概述 由于受到计算机性能的影响&#xff0c;虽然LeNet在图像分类中取得了…...

React中setState什么时候是同步的,什么时候是异步的?

本文内容均针对于18.x以下版本 setState 到底是同步还是异步&#xff1f;很多人可能都有这种经历&#xff0c;面试的时候面试官给了你一段代码&#xff0c;让你说出输出的内容&#xff0c;比如这样&#xff1a; constructor(props) {super(props);this.state {val: 0}}compo…...

优秀开源软件的类,都是怎么命名的?

日常编码中&#xff0c;代码的命名是个大的学问。能快速的看懂开源软件的代码结构和意图&#xff0c;也是一项必备的能力。 Java项目的代码结构&#xff0c;能够体现它的设计理念。Java采用长命名的方式来规范类的命名&#xff0c;能够自己表达它的主要意图。配合高级的 IDEA&…...

绘制CSP的patterns矩阵图

最近在使用FBCSP处理数据,然后就想着看看处理后的样子,用地形图的形式表现出来,但是没有符合自己需求的函数可以实现,就自己尝试的实现了一下,这里记录一下,方便以后查阅。 绘制CSP的patterns矩阵图 对数据做了FBCSP处理,但是想画一下CSP计算出来的patterns的地形图,并…...

Datatables展示数据(表格合并、日期计算、异步加载数据、分页显示、筛选过滤)

系列文章目录 datatable 自定义筛选按钮的解决方案Echarts实战案例代码(21)&#xff1a;front-endPage的CJJTable前端分页插件ajax分页异步加载数据的解决方案 文章目录系列文章目录前言一、html容器构建1.操作按钮2.表格构建二、时间日期计算三、dataTables属性配置1.调用2.过…...

Python decimal模块的使用

Python decimal 模块Python中的浮点数默认精度是15位。Decimal对象可以表示任意精度的浮点数。getcontext函数用于获取当前的context环境&#xff0c;可以设置精度、舍入模式等参数。#在context中设置小数的精度 decimal.getcontext().prec 100通过字符串初始化Decimal类型的变…...

pycharm常用快捷键

编辑类&#xff1a; Ctrl D 复制选定的区域或行 Ctrl Y 删除选定的行 Ctrl Alt L 代码格式化 Ctrl Alt O 优化导入&#xff08;去掉用不到的包导入&#xff09; Ctrl 鼠标 简介/进入代码定义 Ctrl / 行注释 、取消注释 Ctrl 左方括号 快速跳到代码开头 Ctrl 右方括…...

useCallback 与 useMemo 的区别 作用

useCallback 缓存钩子函数&#xff0c;useMemo 缓存返回值&#xff08;计算结果&#xff09;。 TS声明如下&#xff1a;type DependencyList ReadonlyArray<any>;function useCallback<T extends (...args: any[]) > any>(callback: T, deps: DependencyList)…...

Mybatis的学习

01-mybatis传统dao开发模式 概述 mybatis有两种使用模式: ①传统dao开发模式, ②dao接口代理开发模式 ①传统dao开发模式 dao接口 dao实现子类 mapper映射文件dao实现子类来决定了dao接口的方法和mapper映射文件的statement的关系 代码实现 public class StudentDaoImpl im…...

PyTorch深度学习实战 | 计算机视觉

深度学习领域技术的飞速发展&#xff0c;给人们的生活带来了很大改变。例如&#xff0c;智能语音助手能够与人类无障碍地沟通&#xff0c;甚至在视频通话时可以提供实时翻译&#xff1b;将手机摄像头聚焦在某个物体上&#xff0c;该物体的相关信息就会被迅速地反馈给使用者&…...

力扣(LeetCode)436. 寻找右区间(2023.03.10)

给你一个区间数组 intervals &#xff0c;其中 intervals[i] [starti, endi] &#xff0c;且每个 starti 都 不同 。 区间 i 的 右侧区间 可以记作区间 j &#xff0c;并满足 startj > endi &#xff0c;且 startj 最小化 。 返回一个由每个区间 i 的 右侧区间 在 interv…...

已解决Servlet中Request请求参数中文乱码的问题

&#x1f4cb; 个人简介 &#x1f496; 作者简介&#xff1a;大家好&#xff0c;我是阿牛&#xff0c;全栈领域优质创作者。&#x1f61c;&#x1f4dd; 个人主页&#xff1a;馆主阿牛&#x1f525;&#x1f389; 支持我&#xff1a;点赞&#x1f44d;收藏⭐️留言&#x1f4d…...

【flask】URL和视图映射

目录 首页 传参 URL数据类型 get传参 首页 url与视图函数的映射是通过app.route()装饰器实现的。 只有一个斜杠代表的是根目录——首页。 传参 URL传参是通过<参数名称>的形式进行传递。URL中有几个参数&#xff0c;在视图函数中也要指定几个参数 from flask im…...

Python实现性能测试(locust)

一、安装locustpip install locust -- 安装&#xff08;在pycharm里面安装或cmd命令行安装都可&#xff09;locust -V -- 查看版本&#xff0c;显示了就证明安装成功了或者直接在Pycharm中安装locust:搜索locust并点击安装&#xff0c;其他的第三方包也可以通过这种方式二、loc…...

【数论】试除法判断质数,分解质因数,筛质数

Halo&#xff0c;这里是Ppeua。平时主要更新C语言&#xff0c;C&#xff0c;数据结构算法......感兴趣就关注我吧&#xff01;你定不会失望。 &#x1f308;个人主页&#xff1a;主页链接 &#x1f308;算法专栏&#xff1a;专栏链接 现已更新完KMP算法、排序模板&#xff0c;之…...

【C++】红黑树

文章目录红黑树的概念红黑树的性质特征红黑树结点的定义红黑树的插入操作情况1情况2情况3特殊情况代码实现红黑树的验证红黑树的删除红黑树和AVL树的比较红黑树的应用红黑树的概念 红黑树&#xff0c;是一种二叉搜索树&#xff0c;但是每一个结点都增加一个存储位表示结点的颜…...

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…...

Java如何权衡是使用无序的数组还是有序的数组

在 Java 中,选择有序数组还是无序数组取决于具体场景的性能需求与操作特点。以下是关键权衡因素及决策指南: ⚖️ 核心权衡维度 维度有序数组无序数组查询性能二分查找 O(log n) ✅线性扫描 O(n) ❌插入/删除需移位维护顺序 O(n) ❌直接操作尾部 O(1) ✅内存开销与无序数组相…...

【JVM】- 内存结构

引言 JVM&#xff1a;Java Virtual Machine 定义&#xff1a;Java虚拟机&#xff0c;Java二进制字节码的运行环境好处&#xff1a; 一次编写&#xff0c;到处运行自动内存管理&#xff0c;垃圾回收的功能数组下标越界检查&#xff08;会抛异常&#xff0c;不会覆盖到其他代码…...

定时器任务——若依源码分析

分析util包下面的工具类schedule utils&#xff1a; ScheduleUtils 是若依中用于与 Quartz 框架交互的工具类&#xff0c;封装了定时任务的 创建、更新、暂停、删除等核心逻辑。 createScheduleJob createScheduleJob 用于将任务注册到 Quartz&#xff0c;先构建任务的 JobD…...

第一篇:Agent2Agent (A2A) 协议——协作式人工智能的黎明

AI 领域的快速发展正在催生一个新时代&#xff0c;智能代理&#xff08;agents&#xff09;不再是孤立的个体&#xff0c;而是能够像一个数字团队一样协作。然而&#xff0c;当前 AI 生态系统的碎片化阻碍了这一愿景的实现&#xff0c;导致了“AI 巴别塔问题”——不同代理之间…...

unix/linux,sudo,其发展历程详细时间线、由来、历史背景

sudo 的诞生和演化,本身就是一部 Unix/Linux 系统管理哲学变迁的微缩史。来,让我们拨开时间的迷雾,一同探寻 sudo 那波澜壮阔(也颇为实用主义)的发展历程。 历史背景:su的时代与困境 ( 20 世纪 70 年代 - 80 年代初) 在 sudo 出现之前,Unix 系统管理员和需要特权操作的…...

C++ 求圆面积的程序(Program to find area of a circle)

给定半径r&#xff0c;求圆的面积。圆的面积应精确到小数点后5位。 例子&#xff1a; 输入&#xff1a;r 5 输出&#xff1a;78.53982 解释&#xff1a;由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982&#xff0c;因为我们只保留小数点后 5 位数字。 输…...

Spring是如何解决Bean的循环依赖:三级缓存机制

1、什么是 Bean 的循环依赖 在 Spring框架中,Bean 的循环依赖是指多个 Bean 之间‌互相持有对方引用‌,形成闭环依赖关系的现象。 多个 Bean 的依赖关系构成环形链路,例如: 双向依赖:Bean A 依赖 Bean B,同时 Bean B 也依赖 Bean A(A↔B)。链条循环: Bean A → Bean…...

[大语言模型]在个人电脑上部署ollama 并进行管理,最后配置AI程序开发助手.

ollama官网: 下载 https://ollama.com/ 安装 查看可以使用的模型 https://ollama.com/search 例如 https://ollama.com/library/deepseek-r1/tags # deepseek-r1:7bollama pull deepseek-r1:7b改token数量为409622 16384 ollama命令说明 ollama serve #&#xff1a…...

SQL Server 触发器调用存储过程实现发送 HTTP 请求

文章目录 需求分析解决第 1 步:前置条件,启用 OLE 自动化方式 1:使用 SQL 实现启用 OLE 自动化方式 2:Sql Server 2005启动OLE自动化方式 3:Sql Server 2008启动OLE自动化第 2 步:创建存储过程第 3 步:创建触发器扩展 - 如何调试?第 1 步:登录 SQL Server 2008第 2 步…...