个人主页:C++忠实粉丝
欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 C++忠实粉丝 原创读者写者问题与读写锁
收录于专栏[Linux学习]
本专栏旨在分享学习Linux的一点学习笔记,欢迎大家在评论区交流讨论💌
目录
读者写者 VS 生产者消费者模型
读者写者问题跟生产者消费者模型十分类似,同样遵循 “321” 原则
1:一个交易场所
2:2种角色:读者与写者
3:三种关系:写者和写者(互斥)读者与写者(互斥&&同步)读者与读者(并发或者没有关系)
所以它们两个的区别已经很明显了,读者写者中读者与读者之间是并发关系,而生产者消费者中消费者与消费者之间是互斥关系!因为消费者会把数据取走,而读者不会!!
从代码角度分析
读者写者伪代码:
公共部分 :
uint32_t reader_count = 0;
lock_t count_lock;
lock_t writer_lock;
读者:Reader:
// 加锁
lock(count_lock);
if(reader_count == 0)
lock(writer_lock);
++reader_count;
unlock(count_lock);
// read;
//解锁
lock(count_lock);
--reader_count;
if(reader_count == 0)
unlock(writer_lock);
unlock(count_lock);
写者:Writer:
lock(writer_lock);
// write
unlock(writer_lock);
我们可以看到,当第一个读者读数据的时候,它会锁住写锁,然后读者数量加1,后面的读者就不需要,直接读取数据即可,只有当所有读者退出后,才会释放写锁,写者就可以进行写数据。
读写锁
在编写多线程的时候,有一种情况是十分常见的。那就是,有些公共数据修改的机会比较少。相比较改写,它们读的机会反而高的多。通常而言,在读的过程中,往往伴随着查找的操作,中间耗时很长。给这种代码加锁,会极大地降低我们程序的效率。那么有没有一种方法,可以专门处理这种多读少写的情况呢?
有,那就是我们今天的主角 —— 读写锁
读写锁的行为:
当前锁状态 | 读锁请求 | 写锁请求 |
---|---|---|
无锁 | 可以 | 可以 |
读锁 | 可以 | 阻塞 |
写锁 | 阻塞 | 阻塞 |
注意 :写独占,读共享,读锁优先级高!
读写接口:
设置读写优先
int pthread_rwlockattr_setkind_np(pthread_rwlockattr_t *attr, intpref);
/*
pref 共有 3 种选择
PTHREAD_RWLOCK_PREFER_READER_NP (默认设置) 读者优先,可能会导致写者饥饿情况
PTHREAD_RWLOCK_PREFER_WRITER_NP 写者优先,目前有 BUG,导致表现行为和PTHREAD_RWLOCK_PREFER_READER_NP 一致
PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP 写者优先,但写者不能递归加锁
*/
初始化
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);
读写锁案例
/*
* @Author: 13177709051 3506463075@qq.com
* @Date: 2024-12-20 20:44:37
* @LastEditors: 13177709051 3506463075@qq.com
* @LastEditTime: 2024-12-20 20:47:04
* @FilePath: /linux38/test.cc
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <vector>
#include <cstdlib>
#include <ctime>
// 共享资源
int shared_data = 0;
// 读写锁
pthread_rwlock_t rwlock;
// 读者线程函数
void *Reader(void *arg)
{
//sleep(1); //读者优先,一旦读者进入&&读者很多,写者基本就很难进入了
int number = *(int *)arg;
while (true)
{
pthread_rwlock_rdlock(&rwlock); // 读者加锁
std::cout << "读者-" << number << " 正在读取数据, 数据是: " << shared_data << std::endl;
sleep(1); // 模拟读取操作
pthread_rwlock_unlock(&rwlock); // 解锁
}
delete (int*)arg;
return NULL;
}
// 写者线程函数
void *Writer(void *arg)
{
int number = *(int *)arg;
while (true)
{
pthread_rwlock_wrlock(&rwlock); // 写者加锁
shared_data = rand() % 100; // 修改共享数据
std::cout << "写者- " << number << " 正在写入. 新的数据是: " << shared_data << std::endl;
sleep(2); // 模拟写入操作
pthread_rwlock_unlock(&rwlock); // 解锁
}
delete (int*)arg;
return NULL;
}
int main()
{
srand(time(nullptr)^getpid());
pthread_rwlock_init(&rwlock, NULL); // 初始化读写锁
// 可以更高读写数量配比,观察现象
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)
{
int *id = new int(i);
pthread_create(&threads[i], NULL, Reader, id);
}
// 创建写者线程
for (int i = reader_num; i < total; ++i)
{
int *id = new int(i - reader_num);
pthread_create(&threads[i], NULL, Writer, id);
}
// 等待所有线程完成
for (int i = 0; i < total; ++i)
{
pthread_join(threads[i], NULL);
}
pthread_rwlock_destroy(&rwlock); // 销毁读写锁
return 0;
}
当读者与写者都是2的时候:
很明显,我们有写者饥饿问题!
当读者与写者为1和5 时:
这时候由于这又一个读者的原因,读完就会释放锁,那么写者就可以及时写数据。
读写者优先
读者优先
在这种策略种,系统会尽可能多地允许多个读者同时访问资源(比如共享文件或数据)而不会优先考虑写者。这意味着当有读者正在读取数据时,新到达的读者会立即被允许进入读取区,而写者会被长期阻塞,直到所有读者都离开读取区。读者优先策略可能会导致写者饥饿(即写者长时间无法获得写入权限),特别是当读者频繁到达时。
写者优先
在这种策略中,系统会优先考虑写者。当写者请求写入权限时,系统会尽快地让写者进入写入区,即使此时有读者正在读取。这通常意味着一旦写者到达,所有后续的读者都会被阻塞,直到写者完成写入并离开写入区。写者优先策略可以减少写者等待的时间,但可能会导致读者饥饿(即读者长时间无法获取权限),特别是当写者频繁到达时。