一、概念铺垫
生产者与消费者模型 VS 读写模型:
学过的生产者与消费者模型的都知道 321 原则:3种关系,2种角色,1个交易场所(临界资源、共享资源);3种关系就是:生产者与生产者:互斥,消费者与消费者:互斥,生产者与消费者:同步与互斥;2种角色:消费者、生产者;
你们过渡到我们的读写模型,读写模型根生产者与消费者模型差不多,也是遵循321原则,2种角色:读者、写者;3种关系:读者与读者:并发(说人话就是没有关系),写者与写者:互斥;读者与写者:同步与互斥;
为什么读者和读者的关系是:并发?
答:有一篇文章,一个人读和多个人读这篇文章对这篇文章不会改变什么,所以允许多个读者读,但是只允许一个写着写,因为:多个写者写数据,会造成数据的不一致问题;
那么,为什么生产者与消费者中,消费者与消费者之间的关系是:互斥呢?
答:我在菜市场买菜,你也在菜市场买菜,假设只有一颗白菜,我买了,你就不能买了,在代码中就是:我读数据,你也读数据,假设完整的数据是:hello world ,我读了 hello ,你就只能读 world 的,此时的数据是不完整的,所以消费者与消费者的关系只能是:互斥;
本质就是是否影响数据或者说改变数据;
读者与写者模型应用场景:读者很多,写者较少的情况;
二、原理剖析
公共部分:
cpp
uint32_t reader_count = 0;//读者个数
lock_t count_lock;//给读者加锁
lock_t writer_lock;//给写者加锁
读者:
cpp
// 加锁
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);//解锁
写者:
cpp
lock(writer_lock);
// write
unlock(writer_lock);
假设有5个读者,5个写者,那么从读者角度看:
当第一个读者进入到读者代码的时,因为他是第一个读者,而且读者和写者之间的关系是同步与互斥,所以就把写者的锁申请了,让写者不要写了;然后不断的有多个读者读,读完之后,读者个数依次减减,当减到 0 时,那么该读者就是最后一个读者,代表者读结束了,让写者写吧;
那么从写者角度看:
假设5个写者,前两个依次写入了数据,那么此时如果写者的锁让读者申请到了,那么此时写者就不能写了,这就意味着,必须让全部的读者读了数据之后,写者才能写;
三、案例
下面这个案例由豆包提供:

cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
// 全局共享数据:模拟业务共享变量
int g_data = 100;
// 定义全局读写锁
pthread_rwlock_t rwlock;
/**
* @brief 读线程执行函数
* 功能:获取读锁、读取共享数据、释放读锁
* 特性:多个读线程可同时执行
*/
void *read_func(void *arg)
{
// 获取当前线程编号
int tid = *(int *)arg;
// 加读锁:读锁共享,多个线程可同时加锁
pthread_rwlock_rdlock(&rwlock);
printf("【读线程%d】获取读锁成功,开始读取数据,当前数据:%d\n", tid, g_data);
// 模拟业务读取耗时(1秒),放大并发效果
sleep(1);
printf("【读线程%d】数据读取完成,释放读锁\n\n", tid);
// 释放读写锁(读锁/写锁统一使用unlock释放)
pthread_rwlock_unlock(&rwlock);
free(arg); // 释放动态分配的线程编号内存
return NULL;
}
/**
* @brief 写线程执行函数
* 功能:获取写锁、修改共享数据、释放写锁
* 特性:写锁独占,读写、写写全部互斥
*/
void *write_func(void *arg)
{
// 获取当前线程编号
int tid = *(int *)arg;
// 加写锁:写锁独占,所有读写线程阻塞等待
pthread_rwlock_wrlock(&rwlock);
printf("【写线程%d】获取写锁成功,开始修改数据,原数据:%d\n", tid, g_data);
// 模拟数据写入耗时(1秒)
sleep(1);
// 修改共享数据
g_data += 100;
printf("【写线程%d】数据修改完成,新数据:%d,释放写锁\n\n", tid, g_data);
// 释放写锁
pthread_rwlock_unlock(&rwlock);
free(arg);
return NULL;
}
int main()
{
pthread_t tid;
int i, ret;
// 1. 初始化读写锁
ret = pthread_rwlock_init(&rwlock, NULL);
if (ret != 0)
{
perror("pthread_rwlock_init failed");
return -1;
}
printf("读写锁初始化完成,初始数据:%d\n\n", g_data);
// 2. 创建3个读线程:测试【读读共享】
for (i = 1; i <= 3; i++)
{
int *p = malloc(sizeof(int));
*p = i;
ret = pthread_create(&tid, NULL, read_func, p);
if (ret != 0)
{
perror("pthread_create read failed");
return -1;
}
}
// 主线程休眠3秒,等待第一批读线程执行完毕
sleep(3);
// 3. 创建2个写线程:测试【写写互斥】
for (i = 1; i <= 2; i++)
{
int *p = malloc(sizeof(int));
*p = i;
ret = pthread_create(&tid, NULL, write_func, p);
if (ret != 0)
{
perror("pthread_create write failed");
return -1;
}
}
// 主线程休眠4秒,等待写线程执行完毕
sleep(4);
// 4. 创建2个读线程:测试【读写互斥】(写完再读)
for (i = 4; i <= 5; i++)
{
int *p = malloc(sizeof(int));
*p = i;
ret = pthread_create(&tid, NULL, read_func, p);
if (ret != 0)
{
perror("pthread_create read failed");
return -1;
}
}
// 休眠等待所有子线程执行完成
sleep(3);
// 5. 销毁读写锁,释放系统资源
pthread_rwlock_destroy(&rwlock);
printf("所有线程执行完毕,读写锁销毁成功\n");
return 0;
}
四、读者优先 VS 写者优先策略
读者优先:
在这种策略中,系统会尽可能多地允许多个读者同时访问资源(比如共享文件或数据),而不会优先考虑写者。这意味着当有读者正在读取时,新到达的读者会立即被允许进入读取区,而写者则会被阻塞,直到所有读者都离开读取区。读者优先策略可能会导致写者饥饿(即写者长时间无法获得写入权限),特别是当读者频繁到达时------写者饥饿问题。
说人话就是:
自习室里有 3 个人在看书,这时来了一个写者要修改资料。 门外又来了 10 个读者,他们可以直接进去看书,不用管写者。 写者只能一直等,直到这 13 个人全部看完离开,他才能进去。 如果读者源源不断,写者可能从早等到晚,永远进不去。
注意:上面案例的代码和原理剖析的代码都是读者优先;
写者优先:
在这种策略中,系统会优先考虑写者。当写者请求写入权限时,系统会尽快地让写者进入写入区,即使此时有读者正在读取。这通常意味着一旦有写者到达,所有后续的读者都会被阻塞,直到写者完成写入并离开写入区。写者优先策略可以减少写者等待的时间,但可能会导致读者饥饿(即读者长时间无法获得读取权限),特别是当写者频繁到达时------读者饥饿问题。
说人话就是:
自习室里有 3 个人在看书,这时来了一个写者要修改资料。 门外又来了 10 个读者,他们必须原地等着,不能进去。 里面的 3 个人看完离开后,写者直接进去修改资料,读者只能继续等。 如果写者源源不断,读者可能从早等到晚,永远进不去。
设置读写者优先策略的函数:
cpp
#include <pthread.h>
int pthread_rwlockattr_setkind_np(
pthread_rwlockattr_t *attr,
int kind
);
写者优先的伪代码:
cpp
// 公共部分:
uint32_t reader_count = 0; // 当前正在读的读者个数
uint32_t writer_count = 0; // 当前正在写或等待写的写者个数
lock_t count_lock; // 保护 reader_count 和 writer_count 的互斥锁
lock_t writer_lock; // 写者互斥锁,读者也会占用以阻止写者
lock_t read_try; // 读者排队锁,写者优先时用于阻塞新读者
// 读者:
lock(read_try); // 申请读权限,若有写者在等待则阻塞(写者优先)
lock(count_lock);
reader_count++;
if(reader_count == 1) { // 第一个读者
lock(writer_lock); // 锁住写者锁,阻止写者写入
}
unlock(count_lock);
unlock(read_try); // 允许其他读者排队(但可能被写者阻塞)
// 多个读者可以同时进行读操作
// do reading ...
lock(count_lock);
reader_count--;
if(reader_count == 0) { // 最后一个读者
unlock(writer_lock); // 释放写者锁,让等待的写者有机会写
}
unlock(count_lock);
// 写者:
lock(count_lock);
writer_count++;
if(writer_count == 1) { // 第一个写者
lock(read_try); // 锁住读者排队锁,阻止新读者进入
}
unlock(count_lock);
lock(writer_lock); // 等待当前正在读的读者完成(若读者正在读则被阻塞)
// do writing ... // 写操作
unlock(writer_lock);
lock(count_lock);
writer_count--;
if(writer_count == 0) { // 最后一个写者
unlock(read_try); // 释放读者排队锁,允许新读者进入
}
unlock(count_lock);
