一、读者写者问题
之前学过的生产消费者模型中,有321原则,也就是:
三种关系,两个角色,一个交易场所。
同样地:
读者写着问题也有321原则:
-
三种关系:
-
- 写者和写者:它们是竞争关系,一个人写的时候,另一个人不能同时也写,所以需要基本的互斥条件。
-
- 读者和写者:当读者在读的时候,写者不能写数据,得等读者读完才能写,而当写者在写时,读者也不能立刻读,得等写者写完才能读。所以也需要基本的同步和互斥条件。
-
- 读者和读者:读者和读者之间是并发关系,其实也就是没有关系,一个人读完,不影响另一个人读。因为读者并不会取走数据。
-
两个角色:读者和写者
-
一个交易场所。
读者写者问题vs生产消费模型
它们两个模型最本质的区别在于
读者和读者与消费者和消费者
消费者和消费者之间,消费者会带走数据,注定他们是竞争关系。
而读者和读者之间,不会带走数据!!!
其他没什么区别。
理解读者写者模型的原理
下面给一段伪代码来理解:
公共部分:
uint32_t reader_count = 0;
lock_t count_lock;
lock_t writer_lock;
由于读者之间是并发关系,所以,reader_count是把计数器,是为了统计访问临界资源的读者的个数。而count_lock是为了给计数器上锁的,因为有多个读者,防止同时对计数器修改,要加锁保护。
而读者和写者,写者和写者之间是互斥关系,所以要有一把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);
第一个读者进来时,会先申请计数器的锁,并判断计数器资源是否== 0,如果为0,表明我是第一个读者,我会把写者锁申请走,这样当写者想要申请锁来写时,就要阻塞等待锁就绪,相当于防止写者进来了,这样就完成了读者和写者的互斥。然后再对计数器++
当读者读完离开时,会申请计数器的锁,进行计数器的- -,并判断,当前在访问临界资源的读者是否为0,如果为0,就会释放写者锁,让写者能进来写。
Writer
lock(writer_lock);
// write
unlock(writer_lock);
这里不用解释了,就完成了写者和写者,写者和读者的互斥。
读者优先和写者优先
- 读者优先(Reader-Preference)
在这种策略中,系统会尽可能多地允许多个读者同时访问资源(比如共享文件或数据),而不会优先考虑写者。这意味着当有读者正在读取时,新到达的读者会立即被允许进入读取区,而写者则会被阻塞,直到所有读者都离开读取区。读者优先策略可能会导致写者饥饿(即写者长时间无法获得写入权限),特别是当读者频繁到达时。
- 写者优先(Writer-Preference)
在这种策略中,系统会优先考虑写者。当写者请求写入权限时,系统会尽快地让写者进入写入区,即使此时有读者正在读取。这通常意味着一旦有写者到达,所有后续的读者都会被阻塞,直到写者完成写入并离开写入区。写者优先策略可以减少写者等待的时间,但可能会导致读者饥饿(即读者长时间无法获得读取权限),特别是当写者频繁到达时。
要注意的是,不管是读者饥饿问题还是写者饥饿问题,都不是一个弊端,这都只是在不同优先情况下表现出来的特点。
从代码角度理解读者写者模型
读写锁使用的基本接口
初始化
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);
先看基本的 接口使用
#include <iostream>
#include <unistd.h>
#include <pthread.h>
pthread_rwlock_t _rwlock;
static int shared_num = 0;
void* Reader(void* argc)
{
int id = *(int*)argc;
while(1)
{
//读者申请锁
pthread_rwlock_rdlock(&_rwlock);
std::cout << "读者-" << id << "正在读资源: " << shared_num << std::endl;
//read
pthread_rwlock_unlock(&_rwlock);
sleep(1);
}
delete (int*)argc;
return nullptr;
}
void* Writer(void* argc)
{
int id = *(int*)argc;
while(1)
{
pthread_rwlock_wrlock(&_rwlock);
++shared_num;
std::cout << "写者-" << id << "正在写资源: " << shared_num << std::endl;
sleep(1); //write
pthread_rwlock_unlock(&_rwlock);
}
delete (int*)argc;
return nullptr;
}
//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 写者优先,但写者不能递归加锁
*/
int main()
{
pthread_rwlock_init(&_rwlock,nullptr);
const int readers = 2;
const int writers = 2;
const int total = readers +writers;
pthread_t threads[total]; //管理线程id
//创建读者和写者线程
for(int i = 0;i < readers;++i)
{
int *ip = new int(i);
pthread_create(&threads[i],NULL,Reader,ip);
}
for(int i = readers;i < total;++i)
{
int *id = new int(i - readers);
pthread_create(&threads[i],NULL,Writer,id);
}
//等待线程
for(int i = 0;i < total;i++)
{
pthread_join(threads[i],NULL);
}
//线程分离
// for(int i = 0;i < total;++i)
// {
// pthread_detach(threads[i]);
// }
pthread_rwlock_destroy(&_rwlock);
return 0;
}
在设置优先级时,尝试过,但是失败了,出现段错误,不知道怎么回事。
- 解析上面的代码:
- 默认是读者优先,所以,读者在申请读写锁时,写者会阻塞住,直到所有读者离开临界区,写者才能进入。