news 2026/5/25 12:11:46

Linux学习日记21:读写锁

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux学习日记21:读写锁

一、前言

前面我们学习了死锁的相关知识,今天我们来学习读写锁的相关知识。

二、读写锁

读写锁是 Linux 系统中一种支持并发读、独占写的同步机制,核心设计目标是提高读多写少场景下的并发性能,解决互斥锁(mutex)在多读场景下的性能瓶颈。

2.1、读写锁的核心思想

读写锁遵循“读者共享,写者独占”的原则,将访问者分为两类角色:读者(Reader):仅读取共享资源,不修改。多个读者可以同时持有锁,互不阻塞;写者(Writer):修改共享资源。写者必须独占锁,同一时间只能有一个写者,且写者与读者互斥。

这种设计的优势在于:当共享资源以读操作为主时,读写锁的并发效率远高于互斥锁(互斥锁会强制所有读者串行执行)。

2.2、读写锁的特性

1、互斥原则

读者与读者:不互斥,允许多个读者同时加锁。

读者与写者:互斥,读者持有锁时写者阻塞,反之亦然。

写者与写者:互斥,多个写者串行执行。

2、锁的状态

读写锁内部维护两个核心状态:

读者计数:记录当前持有锁的读者数量;写者标记:标记是否有写者持有锁或等待锁。

3、工作模式

公平模式:遵循 FIFO 原则,等待队列中的读者和写者按顺序获取锁,避免写者饥饿。

非公平模式(默认):一般来说,Linux中的pthread_rwlock更偏向于写者优先,具体表现是,一旦有写线程在等待,新来的读线程通常会被阻塞,等写线程完成后再放行读线程。

2.3、读写锁场景练习

1、线程A加写锁成功,线程B请求读锁:线程B阻塞;

2、线程A持有读锁,线程B请求写锁:线程B阻塞;

3、线程A拥有读锁,线程B请求读锁:线程B加锁;

4、线程A持有读锁,然后线程B请求写锁,然后线程C请求读锁:线程B阻塞,线程C阻塞 ;线程B加锁,线程C阻塞, 线程C加锁;

5、线程A持有写锁,然后线程B请求读锁,然后线程C请求写锁:线程B阻塞,线程C阻塞;线程C加锁, 线程B阻塞,线程B加锁;

2.4、读写锁相关函数

1、初始化读写锁

函数原型如下:

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

功能:初始化一个读写锁对象,分配必要的资源。

参数: rwlock:指向待初始化的读写锁变量;

attr:读写锁属性,NULL表示使用默认属性(非公平模式);

返回值:成功:0;失败:非 0 错误码。

注:未初始化的读写锁不能直接使用;初始化后的锁必须通过pthread_rwlock_destroy销毁。

2、销毁读写锁

函数原型如下:

int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

功能:销毁读写锁,释放其占用的资源。

参数:rwlock:指向已初始化的读写锁。

返回值:成功:0;失败:非0错误码;

注:销毁前必须确保锁未被任何线程持有,否则行为未定义。

3、读者阻塞加锁

函数原型如下:

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

功能:读者申请读锁,若锁被写者持有则阻塞,直到获取锁。

返回值:成功 0;失败非 0 错误码。

:多个读者可同时持有读锁,与写者互斥。

4、写者阻塞加锁

函数原型如下:

int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

功能:写者申请写锁,若锁被读者 / 其他写者持有则阻塞,直到独占锁。

返回值:成功 0;失败非 0 错误码。

:写锁为独占锁,同一时间仅一个写者持有。

5、读者尝试加锁(非阻塞)

函数原型如下:

int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);

功能:读者尝试获取读锁,若锁被占用(写者持有 / 写者等待),立即返回失败,不阻塞;

返回值:成功:0,失败:EBUSY(锁被占用),EINVAL(参数无效);

6、写者尝试加锁(非阻塞)

函数原型如下:

int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);

功能:写者尝试获取写锁,若锁被读者 / 其他写者占用,立即返回失败。

返回值:成功:0,失败:EBUSY(锁被占用),EINVAL(参数无效);

7、 解锁(统一接口)

函数原型如下:

int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

功能:释放读锁或写锁(无需区分读写,锁内部自动识别)。

返回值:成功 0;失败:EINVAL(锁无效)、EPERM(当前线程未持有锁)。

注:读者加锁后必须由同一线程解锁,写者同理。读锁解锁时,若读者计数归 0,才会唤醒等待的写者;写锁解锁时,会唤醒所有等待的读者 / 写者(按优先级)。

2.5、典型示例

我们来完成下面一个小练习:三个线程不定时写同一个全局变量,五个线程不定时期读同一全局资源。

首先创建一个文件pthread_rwlock.c文件,输入以下代码:

#include <stdio.h> #include <pthread.h> #include <unistd.h> //不加锁 int number = 10000; void *write_myfun(void *arg) { while(1) { number++; printf("write: %ld %d\n",pthread_self(),number); usleep(500); } } void *read_myfun(void *arg) { while(1) { printf("read: %ld %d\n",pthread_self(),number); usleep(500); } } int main() { pthread_t p[8]; for(int i=0;i<3;++i) { pthread_create(&p[i],NULL,write_myfun,NULL); } for(int i=3;i<8;++i) { pthread_create(&p[i],NULL,read_myfun,NULL); } for(int i=0;i<8;++i) { pthread_join(p[i],NULL); } return 0; }

编译并运行,结果如下:

截取其中的一段来看,可以看到第二个的write比第一个还小,说明两个写操作可能同时在执行,数据可能被交叉覆盖;第二个read的标识是17107,远早于之前write17109—— 说明读操作没有被写操作正确阻塞,在写操作完成前就读取了旧的缓存数据,或者读锁加锁时机错误。

上面是不加锁的情况,下面我们来加上读写锁:

#include <stdio.h> #include <pthread.h> #include <unistd.h> //加读写锁 int number = 10000; pthread_rwlock_t lock; void *write_myfun(void *arg) { while(1) { pthread_rwlock_wrlock(&lock); number++; printf("write: %ld %d\n",pthread_self(),number); pthread_rwlock_unlock(&lock); usleep(500); } } void *read_myfun(void *arg) { while(1) { pthread_rwlock_rdlock(&lock); printf("read: %ld %d\n",pthread_self(),number); pthread_rwlock_unlock(&lock); usleep(500); } } int main() { pthread_rwlock_init(&lock,NULL); pthread_t p[8]; for(int i=0;i<3;++i) { pthread_create(&p[i],NULL,write_myfun,NULL); } for(int i=3;i<8;++i) { pthread_create(&p[i],NULL,read_myfun,NULL); } for(int i=0;i<8;++i) { pthread_join(p[i],NULL); } pthread_rwlock_destroy(&lock); return 0; }

编译并运行,结果如下:

可以看到加了读写锁的程序运行地很好,没有交叉覆盖的情况。

2.6、读写锁和互斥锁的区别

区别如下:

特性读写锁(rwlock)互斥锁(mutex)
访问模式读者共享、写者独占所有线程独占
并发性能读多写少场景下性能高无论读写,性能固定(串行)
适用场景共享资源以读操作为主共享资源读写频率相当,或写操作频繁
锁状态读者计数 + 写者标记加锁 / 未加锁 二值状态
饥饿问题非公平模式下写者可能饥饿无饥饿问题(公平模式)
接口复杂度较高(区分读写操作)简单(统一加解锁)
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/25 18:05:04

LobeChat能否实现AI营养师?饮食建议与健康管理助手

LobeChat能否实现AI营养师&#xff1f;饮食建议与健康管理助手 在数字健康浪潮席卷全球的今天&#xff0c;一个普通人想要科学减脂、控制血糖或改善饮食结构&#xff0c;不再只能依赖昂贵且难约的营养门诊。越来越多用户开始期待&#xff1a;能否有一个24小时在线、懂专业又懂…

作者头像 李华
网站建设 2026/5/26 3:32:52

亚马逊推广新引擎:DSP广告的精准转化法则

当大多数卖家还在为站内关键词竞价而疲惫不堪时&#xff0c;已经有人通过亚马逊DSP广告&#xff0c;以更低的成本获取了更具价值的用户&#xff0c;数据显示&#xff0c;优化后的DSP广告能显著提升关键营销指标&#xff0c;这背后是对全域流量逻辑的深度理解。在亚马逊竞争日益…

作者头像 李华
网站建设 2026/5/26 3:32:52

RocketMQ 高并发场景优化:消息压缩、批量发送与消费线程池调优

在分布式系统中&#xff0c;消息队列作为“削峰填谷”的核心组件&#xff0c;承载着高并发流量下的消息流转重任。RocketMQ 凭借其高吞吐量、低延迟、高可靠的特性&#xff0c;成为众多企业的首选中间件。但在秒杀、大促、日志采集等超高峰值场景下&#xff0c;默认配置的 Rock…

作者头像 李华
网站建设 2026/5/25 12:23:59

9、Vim 中运行 shell 命令及文件管理全解析

Vim 中运行 shell 命令及文件管理全解析 1. 在 Vim 中运行 Shell 命令 在 Vim 里,无需退出就能轻松调用外部程序,还能把缓冲区内容作为标准输入传递给命令,或者用外部命令的标准输出填充缓冲区。不过,这些命令在终端版 Vim 中使用效果最佳,若使用 GVim 或 MacVim,可能没…

作者头像 李华
网站建设 2026/5/25 20:22:15

朋友圈文案润色:LobeChat让你更有格调

LobeChat&#xff1a;让AI对话更有格调 在朋友圈发一条状态&#xff0c;配图是夕阳下的咖啡杯&#xff0c;文字写着“生活需要一点慢”。你犹豫了一下——这句太普通了&#xff0c;有没有更打动人的表达&#xff1f;如果此刻有个懂你情绪、又擅长文字的助手就好了。 现在&#…

作者头像 李华
网站建设 2026/5/26 3:36:33

26、Vim搜索与自动补全功能全解析

Vim搜索与自动补全功能全解析 1. 替代grep插件 在Vim中,将多文件搜索外包给外部程序十分便捷。我们只需更改 grepprg 和 grepformat 设置,然后执行 :grep 命令,搜索结果就会出现在快速修复列表中。无论实际调用的是哪个程序,其接口几乎相同。 不过,不同程序存在重…

作者头像 李华