【Linux】读写锁

一、概念铺垫

生产者与消费者模型 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);
相关推荐
sulikey2 小时前
Linux ext2文件系统结构
linux·操作系统·文件系统·linux文件系统·ext2·ext2文件系统
黄焖鸡能干四碗2 小时前
固定资产管理系统建设方案和源码(Java源码)
大数据·数据库·人工智能·物联网·区块链
EAIReport3 小时前
Agent开发+Vibe Coding:数据本体论筑牢AI开发效率与可靠性双防线
大数据·人工智能
白菜欣3 小时前
Linux — 进程控制
android·linux·运维
踩着两条虫3 小时前
「AI + 低代码」的可视化设计器
开发语言·前端·低代码·设计模式·架构
JoneBB3 小时前
ABAP Webservice连接
运维·开发语言·数据库·学习
皮卡狮3 小时前
Linux开发专属工具
linux
2601_957787583 小时前
企业级内容矩阵的安全合规体系构建与技术实现
大数据·安全·矩阵
即使再小的船也能远航3 小时前
【Python】安装
开发语言·python
weixin_421725263 小时前
Linux 编程语言全解析:C、C++、Python、Go、Rust 谁更强?
linux·python·go·c·编程语言