Linux自旋锁和读写锁

        在前面的文章中我们已经介绍了有关互斥锁的概念与使用,本篇将开始介绍在 Linux 中的自旋锁和读写锁。这三种锁分别用于在不同的应用场景之中,其中互斥锁最为常用,但是我们需要了解一下其他的锁。

        对于自旋锁和读写锁都介绍了其原理以及接口使用,并且给出了样例代码。

目录

自旋锁

1. 自选锁概念和原理

2. 自旋锁的优缺点与接口使用

3. 自旋锁使用样例代码

读写锁

1. 读者写者模型 vs 生产消费模型

2. 读写锁伪代码和读写锁函数接口

3. 读者优先和写者有限

4. 读写锁使用样例代码

自旋锁

        在使用互斥锁对我们的临界资源上锁时,当其他线程抢不到锁,则会在系统调用中阻塞起来(操作系统将线程的 tcb 放入到等待队列中),等到抢到锁之后又会将线程唤醒(将线程的 tcb 从等待队列中放入到运行队列中)。但是假若在临界区运行的时间还没有将线程挂起等待的时间长,那么还不如让线程不断的轮询访问我们的锁,不将线程挂起等待,当锁一但释放就可以立即的获取到锁。

        自旋锁的应用中应用层中使用的非常少,但是在操作系统层面使用得很多,因为操作系统不会轻易的将自己挂起等待,所以对于某些资源需要等到访问的时候,通常都是轮询等待。

1. 自选锁概念和原理

        自旋锁概念:一种多线程同步机制,用于保护共享资源免受并发访问的影响。在多个线程尝试获取到锁时,它们会持续旋转(在循环中不断的检查锁是否可用)而不是立即进入休眠状态等待锁的释放。这种机制减少了线程切换的开销(因为假若是阻塞等待则会将线程挂起然后切换,而轮询则是在时间片内一直访问),适用于短时间内锁竞争的情况。但是假若不合理的使用(在临界区的运行时间较长),则会导致 CPU 的浪费。

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

        1. 若标志位为 false,表示锁可以使用,线程将标志位设置为 true,表示占用了锁,然后进入到临界区中执行代码;        

        2. 若标志位为 true,表示锁已经被其他先占用,线程会在一个循环中不断自旋等待,直到锁被释放。

        对于自旋锁代码实现的伪代码如下:

typedef _Atomic struct
{
#if __GCC_ATOMIC_TEST_AND_SET_TRUEVAL == 1
_Bool __val;
#else
unsigned char __val;
#endif
} atomic_flag;// ATOMIC_FLAG_INIT 值为0
atomic_flag spinlock = ATOMIC_FLAG_INIT;// 尝试获取锁
void spinlock_lock() {// 没有获取到锁则一直运行,返回值为true// 获取到锁,返回值为false,跳出循环等待while (atomic_flag_test_and_set(&spinlock)) {// 如果锁被占用,则忙等待}
}// 释放锁
void spinlock_unlock() {atomic_flag_clear(&spinlock);
}atomic_flag_test_and_set 函数检查 atomic_flag 的当前状态。如果
atomic_flag 之前没有被设置过(即其值为 false 或“未设置”状态),则函数会将其
设置为 true(或“设置”状态),并返回先前的值(在这种情况下为 false)。如果
atomic_flag 之前已经被设置过(即其值为 true),则函数不会改变其状态,但会
返回 true。

        对于以上伪代码的实现,特别是对 atomic_flag 的操作一定得是原子的,这样才能保证 atomic_flag 的读取和修改在多线程环境中是不可分割的,同时这样保证了线程安全的问题。

2. 自旋锁的优缺点与接口使用

        优点:

        1. 低延迟:自旋锁适用于短时间内的锁竞争情况,因为他不会让线程进入休眠状态,从而避免了线程切换的开销,提高了锁操作的效率。

        2. 减少系统调度开销:等待锁的线程不会被阻塞,不需要切换上下文,减少了系统调度的开销。

        缺点:

        1. CPU 资源的浪费:若锁持有的时间较长,等到获取线程的线程会一直循环等待,导致 CPU 资源的浪费。

        2. 可能引起活锁:当多个线程同时自旋等待同一个锁的时候,若没有适当的退避政策,可能会导致所有线程都在不断检查锁的状态,从而无法进入临界区,形成活锁。

        所以对于自旋锁,常用于短暂等待的情况和多线程锁的使用(通常用于底层,同步多个 CPU 对共享资源的访问)。

        自旋锁常用接口的使用:

pthread_spinlock_t // 自旋锁类型int pthread_spin_lock(pthread_spinlock_t *lock);
// 抢不到锁一直等待
int pthread_spin_trylock(pthread_spinlock_t *lock);
// 抢不到锁直接返回
int pthread_spin_unlock(pthread_spinlock_t *lock);
// 解锁int pthread_spin_init(pthread_spinlock_t *lock, int pshared);
// 初始化,第二个参数通常设置为0
int pthread_spin_destroy(pthread_spinlock_t *lock);
// 销毁

3. 自旋锁使用样例代码

        使用自旋锁实现一个抢票逻辑的代码,如下:

#include <iostream>
#include <pthread.h>
#include <unistd.h>pthread_spinlock_t lock;
int tickets = 1000;void* Routine(void* args) {const char* name = static_cast<const char*>(args);while (true) {pthread_spin_lock(&lock);if (tickets > 0) {usleep(1000); // 表示抢票时间std::cout << name << " get a ticket, the remaining tickets are: " << tickets << std::endl;tickets--;pthread_spin_unlock(&lock);} else {pthread_spin_unlock(&lock);break;}}return nullptr;
}int main() {pthread_spin_init(&lock, 0);pthread_t t1, t2, t3, t4;pthread_create(&t1, nullptr, Routine, (void*)"thread-1");pthread_create(&t2, nullptr, Routine, (void*)"thread-1");pthread_create(&t3, nullptr, Routine, (void*)"thread-1");pthread_create(&t4, nullptr, Routine, (void*)"thread-1");pthread_join(t1, nullptr);pthread_join(t2, nullptr);pthread_join(t3, nullptr);pthread_join(t4, nullptr);pthread_spin_destroy(&lock);return 0;
}

        测试结果如下:

读写锁

        在编写多线程代码的时候,也会出现公共数据修改的机会较少的情况,也就是对于写入的数据,很少将其修改,既然不怎么修改,那么在读出数据的时候,也就是说我们根本就不需要加锁,不加锁多线程就可以并行运行。

1. 读者写者模型 vs 生产消费模型

        在读者写者模型中和生产消费模型中的,存在什么区别呢?

        其实读者写者模式和生产消费模型,都遵守321原则:三种关系、两个对象、一个场所(对于生产消费模型,在前文中已经介绍过,这里主要介绍读者写者模型)。

        在读者写者模型中:三种关系为:读者和读者、写者和写者、读者和写者;

        两个对象:读者和写者。

        一个场所:可以是一段内存空间、也可以是某种数据结构。

        让而在三种关系中,写者和写者的关系为互斥,一个时刻只能由一个线程写入信息,防止信息被覆盖,写者和读者的关系为互斥和同步关系,写的时候不能读,读的时候不可以写,但是当读完也需要通知写者来写,写完也需要通知读者来读;而对于读者和读者的关系则不同,读者和读者之间可以共享信息,可以一起读信息,互不干扰,所以读者和读者的关系为没有关系

        既然读者和读者之间没有关系,也就是说明在读消息的时候我们不需要加锁。

2. 读写锁伪代码和读写锁函数接口

        一下为一个读写锁的伪代码,如下:

        对于 Reader而言:

// 加锁
lock(count_lock);
// 当第一个读者进来的时候,要将写者锁住
if(reader_count == 0)lock(writer_lock);
++reader_count;
unlock(count_lock);//解锁
lock(count_lock);
--reader_count;
// 当是最后一个读者离开的时候,将写者唤醒,让写者写入信息
if(reader_count == 0)unlock(writer_lock);
unlock(count_lock);

        对于 Writer 而言:

lock(writer_lock);
// write
unlock(writer_lock);

        对于读写锁的函数接口如下:

// 读写锁变量类型
pthread_rwlock_t// 初始化
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthread_rwlockattr_t *restrict attr);// 销毁
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); // 解锁通用设置读写优先策略函数:
int pthread_rwlockattr_setkind_np(pthread_rwlockattr_t *attr, int pref);
pref的三种取值:
PTHREAD_RWLOCK_PREFER_READER_NP (默认设置) 读者优先,可能会导致写者饥
饿情况
PTHREAD_RWLOCK_PREFER_WRITER_NP 写者优先,目前有 BUG,导致表现行为和 PTHREAD_RWLOCK_PREFER_READER_NP 一致
PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP 写者优先,但写者不能递
归加锁

3. 读者优先和写者有限

        读者优先:

        在这种策略中,系统会尽可能多地允许多个读者同时访问资源,而不会优先考虑写者。这意味着当有读者正在读取时,新达到的读者会立即被运行进入到读取区,而写者会被阻塞,直到所有读者都离开读取区。读者优先策略可能会导致写者饥饿(写者长时间不能获取权限进入),特别是当读者频繁到达时。

        写者优先:

        在这种策略中,系统会优先考虑写者,当写者请求写入权限时,系统会尽可能地让写者进入写入区,即使此时有读者正在读取。意味着一旦有写者到达,所有后续的读者都会被阻塞,直到写者完成写入并离开写入区。写者优先策略可以减少写者等待的时间,但是会导致读者饥饿问题特别是写者频繁到达。

4. 读写锁使用样例代码

        如下,我们使用读写锁给出一个代码样例:

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <ctime>
#include <cstdlib>// 共享资源
int shared_data = 0;// 读写锁
pthread_rwlock_t rwlock;void* Reader(void* args) {// sleep(2); // 读者优先,一旦读者进入,写者就很难进入了const char* name = static_cast<const char*>(args);while (true) {pthread_rwlock_rdlock(&rwlock);std::cout << name << " is reading the shared data, the data is " << shared_data << std::endl;sleep(1); // 模拟读花的时间pthread_rwlock_unlock(&rwlock);}return nullptr;
}void* Writer(void* args) {const char* name = static_cast<const char*>(args);while (true) {pthread_rwlock_wrlock(&rwlock);shared_data = rand() % 100;std::cout << name << " is writing the shared data, the data is " << shared_data << std::endl; sleep(1); // 模拟写花的时间pthread_rwlock_unlock(&rwlock);}return nullptr;
}int main() {srand(time(nullptr) ^ getpid());pthread_rwlock_init(&rwlock, nullptr);const int reader_num = 2;const int writer_num = 2;const int total = reader_num + writer_num;pthread_t threads[total]; // 创建读线程for (int i = 0; i < reader_num; i++) {char* buff = new char[128];snprintf(buff, 128, "reader-%d", i + 1);pthread_create(&threads[i], nullptr, Reader, (void*)buff);}// 创建写线程for (int i = reader_num; i < total; i++) {char* buff = new char[128];snprintf(buff, 128, "writer-%d", i + 1);pthread_create(&threads[i], nullptr, Writer, (void*)buff);}for (int i = 0; i < total; i++) {pthread_join(threads[i], nullptr);}pthread_rwlock_destroy(&rwlock);return 0;
}

        测试结果如下:

        对于如上代码,特别容易出现读者优先,可以通过调整写者的数量和读者进入的时间来调整为写者优先

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

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

相关文章

游戏如何对抗 IL2cppDumper逆向分析

众所周知&#xff0c;Unity引擎中有两种脚本编译器&#xff0c;分别是 Mono 和 IL2CPP 。相较于Mono&#xff0c;IL2CPP 具备执行效率高、跨平台支持等优势&#xff0c;已被大多数游戏采用。 IL2CPP 模式下&#xff0c;可以将游戏 C# 代码转换为 C 代码&#xff0c;然后编译为…

GPT-4o System Card is released

GPT-4o System Card is released, including red teaming, frontier risk evaluations, and other key practices for industrial-strength Large Language Models. https://openai.com/index/gpt-4o-system-card/ 报告链接 企业级生成式人工智能LLM大模型技术、算法及案例实战…

UE5用蓝图实现物体A始终朝向物体B |Find Look at Rotation|

非常常用的蓝图节点 |Find Look at Rotation|&#xff1a;获取 物体A 到 物体B 的Rotator。 Tick中将算出的Rotator设置给物体A&#xff0c;即可实现永远朝向物体B

C++STL之map的使用详解

简介&#xff1a;map底层实现为红黑树&#xff0c;增删查的时间复杂度&#xff1a;O(logn), key是有序的&#xff0c;默认升序 一、初始化 #include<iostream> #include<map> #include<string> using namespace std; int main() {std::map<int, std::st…

楼顶气膜羽毛球馆:城市健身新空间—轻空间

随着城市化进程的加快&#xff0c;城市土地资源愈发紧张&#xff0c;如何高效利用有限的空间成为一大挑战。楼顶气膜羽毛球馆作为一种创新的体育场馆建设方式&#xff0c;凭借其独特的优势&#xff0c;逐渐成为城市健身的新宠。它不仅有效利用了楼顶闲置空间&#xff0c;还为市…

鸿蒙Harmony编程开发:服务端证书锁定防范中间人攻击示例

1. TLS通讯中间人攻击及防范简介 TLS安全通讯的基础是基于对操作系统或者浏览器根证书的信任&#xff0c;如果CA证书签发机构被入侵&#xff0c;或者设备内置证书被篡改&#xff0c;都会导致TLS握手环节面临中间人攻击的风险。其实&#xff0c;这种风险被善意利用的情况还是很…

【25届秋招】饿了么0817算法岗笔试

目录 1. 第一题2. 第二题3. 第三题 ⏰ 时间&#xff1a;2024/08/17 &#x1f504; 输入输出&#xff1a;ACM格式 ⏳ 时长&#xff1a;100min 本试卷还有单选和多选部分&#xff0c;但这部分比较简单就不再展示。 最近终于有时间继续整理之前的笔试题了&#xff0c;因为时间仓促…

数学建模之数据分析【九】:数据清理概述

文章目录 一、什么是数据清理二、为什么数据清理很重要三、执行数据清洁的步骤四、如何执行数据清理五、数据清理的Python库实现5.1 数据检查与探索5.2 使用df.info()检查数据信息5.3 检查分类和数字列5.4 检查分类列中唯一值的总数5.5 执行数据清理的步骤5.5.1 删除所有上述不…

C++ 设计模式——观察者模式

观察者模式 观察者模式主要组成部分例一&#xff1a;工作流程第一步&#xff1a;定义观察者接口第二步&#xff1a;定义主题接口第三步&#xff1a;实现具体主题第四步&#xff1a;实现具体观察者第五步&#xff1a;主函数UML 图UML 图解析 例二&#xff1a;工作流程第一步&…

动态规划之买卖股票篇-代码随想录算法训练营第三十八天| 买卖股票的最佳时机ⅠⅡⅢⅣ,309.最佳买卖股票时机含冷冻期,714.买卖股票的最佳时机含手续费

121. 买卖股票的最佳时机 题目链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 讲解视频&#xff1a; 动态规划之 LeetCode&#xff1a;121.买卖股票的最佳时机1 题目描述&#xff1a; 给定一个数组 prices &#xff0c;它的第 i 个元素 prices[i] 表示一支给定…

[数据集][目标检测]电力场景输电线异物检测数据集VOC+YOLO格式2060张1类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;2060 标注数量(xml文件个数)&#xff1a;2060 标注数量(txt文件个数)&#xff1a;2060 标注…

K8s节点状态 NotReady排查

k8s节点由 Ready变成 NotReady izbp12ghzy6koox6fqt0suz NotReady slave 97d v1.23.3 izbp12ghzy6koox6fqt0svz Ready control-plane,master 98d v1.23.3节点进入 NotReady 状态可能是由于多种原因引起的&#xff0c;尤其是在资源过量分配&am…

环绕音效是什么意思,电脑环绕音效怎么开

Boom 3D是一款专业的音效增强软件&#xff0c;它拥有先进的音效处理技术和丰富的音效设置选项&#xff0c;可以为用户打造出高度定制化的音频体验&#xff0c;Boom 3D还拥有简洁直观的界面&#xff0c;操作简单易懂&#xff0c;即使是音频技术的新手也能轻松上手。本篇文章就将…

微信小程序引入全局环境变量

有时候一套代码要在多个小程序appId下使用,其中又有一些数据(文字)需要做区分.可以使用下面的方法 把要配置的数据以export default 形式导出 在app.js中,引入project.config.0.js文件,将导出的数据放在globalData中 在页面目录中,即可利用getApp()方法使用全局变量 也可以放数…

buuctf [HDCTF2019]Maze

前言&#xff1a;做题笔记。 常规 下载 解压 查壳 脱壳后用32IDA Pro打开。 得&#xff0c;迷宫类型的题目。(字符串有说。) 咳&#xff0c;此前思路对半分不行了。。。 合理猜测步数为&#xff1a;14。 那可以看看7 * 10的迷宫类型。(手动猜测的时候去取倍数如&#xff1a;0 2…

【三维深度补全模型】PENet

【版权声明】本文为博主原创文章&#xff0c;未经博主允许严禁转载&#xff0c;我们会定期进行侵权检索。 参考书籍&#xff1a;《人工智能点云处理及深度学习算法》 本文为专栏《Python三维点云实战宝典》系列文章&#xff0c;专栏介绍地址“【python三维深度学习】python…

shell脚本中$0 $1 $# $@ $* $? $$ 的各种符号意义详解

文章目录 一、概述1.1、普通字符1.2、元字符 二、转义字符$2.1、实例12.2、实例22.3、实例32.4、实例42.5、实例5 三、linux命令执行返回值$?说明 一、概述 shell中有两类字符&#xff1a;普通字符、元字符。 1.1、普通字符 在Shell中除了本身的字面意思外没有其他特殊意义…

校友林小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;树木管理管理&#xff0c;所属科管理&#xff0c;树木领取管理&#xff0c;树跟踪状态管理&#xff0c;用户信息统计管理&#xff0c;树木捐款管理&#xff0c;留言板管理 微信端…

基于vue框架的毕业设计管理系统5n36i(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。

系统程序文件列表 项目功能&#xff1a;学生,教师,课题信息,题目分类,选题信息,任务书,中期检查,提交论文,论文成绩,答辩成绩,校园公告,教研主任,申报课题 开题报告内容 基于Vue框架的毕业设计管理系统开题报告 一、引言 随着高等教育的不断发展&#xff0c;毕业设计作为培…

AITDK SEO扩展:为网站优化提供一站式解决方案

AITDK SEO扩展&#xff1a;为网站优化提供一站式解决方案 想提升你的网站在搜索引擎中的排名&#xff1f;让我们来看看AITDK SEO扩展&#xff0c;它是你网站优化的得力助手&#xff01;在这篇文章中&#xff0c;我将为你介绍AITDK SEO扩展的功能特点&#xff0c;以及它如何帮助…