读者写者问题与读写锁

发布于:2024-12-22 ⋅ 阅读:(164) ⋅ 点赞:(0)

个人主页:C++忠实粉丝
欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 C++忠实粉丝 原创

读者写者问题与读写锁

收录于专栏[Linux学习]
本专栏旨在分享学习Linux的一点学习笔记,欢迎大家在评论区交流讨论💌

目录

读者写者 VS 生产者消费者模型 

从代码角度分析 

读写锁 

读写锁的行为:

读写接口: 

设置读写优先 

初始化

销毁

加锁和解锁

读写锁案例

读写者优先 

读者优先

写者优先 


读者写者 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 时:

这时候由于这又一个读者的原因,读完就会释放锁,那么写者就可以及时写数据。 

读写者优先 

读者优先

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

写者优先 

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


网站公告

今日签到

点亮在社区的每一天
去签到