读写锁的原理与实现

news2025/1/16 8:59:27

文章目录

  • 什么是读写锁
    • 生产消费模型 VS 读写模型
  • 读写锁的pthread库接口
    • 读者&&写者模式
  • 模拟实现读写锁
    • 思路1——用两个锁来实现(读者优先)
      • 模拟实现
    • 思路2——两个条件变量一个锁(写者优先)
      • 模拟实现

可以看看之前写的文章线程里面有关于锁和生产消费者模型的介绍

什么是读写锁

生产消费模型 VS 读写模型

对于生产者和消费者模型的概念图👇
在这里插入图片描述
那么我们读写锁源自读者写着模型,和生产者和消费者模型十分类似👇
在这里插入图片描述
两个的模型实际上是一模一样的,我们还是通过“三二一”来深入了解一下读者写者模型

  • 三种关系
    • 读者-读者:没有关系,共享公共资源
    • 读者-写者:互斥、同步,因为读的时候发生了写或写的时候来读,读取到的信息都是错误的!!
    • 写者-写者:互斥,临界资源中当有一个写者时其他所有人(不论你是读者还是写者)都进不来
  • 两者角色:写者和读者
  • 一个场所:临界区

生产者和写者
两个是一模样的,都是向临界区输入数据的

读者和消费者
消费者会从临界区中拿走数据(修改临界区) 而读者不会(可以理解为对临界区表现为只读)

读写锁的pthread库接口

int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);   /* 销毁RW lock */
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
      const pthread_rwlockattr_t *restrict attr);       /* 初始化RW lock */

pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;   /* 直接赋值方式初始化RW lock */

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);    /* 取得读锁,进入read-mode */
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock); /* 尝试取得读锁,失败立即返回  */

int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock); /* 取得写锁,进入write-mode */
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);    /* 尝试取得写锁,失败立即返回  */

int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);    /* 释放读/写锁 */

这些接口与普通锁的接口类似就不一一介绍了,但是我们要明确的是不管你是读者和写者都是对一把读写锁加锁,不同身份的人通过不同的接口给读写锁加锁或解锁

  • 如果你是读者,你就用int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); 这个接口加锁,解锁以此类推
  • 如果你是写者,你就用int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock); 这个接口加锁,解锁以此类推

读者&&写者模式

关于读写锁要知道读写锁的两个模式:

  • read_mode : 再此模式下,第一个读者取得了读写锁就进入该模式,所有写者都会被阻塞,而读者可以申请到锁,当最后一个读者释放锁,退出该模式
  • write_mode :当一个写者取得读写锁就进入该模式之,所有读者和写者均被阻塞,当申请锁的写者释放锁之后退出该模式

模拟实现读写锁

读写锁的实现的思路最重要的是,不管你的身份是读者还是写者,一定要搞清楚目前临界区域的状态!
我们把状态分为三种:

  • 读模式
  • 写模式
  • 初始状态:既不是读模式,也不是写模式。

思路1——用两个锁来实现(读者优先)

实现思路:两把锁,一把我们叫读锁、一把叫写锁。读者走读锁的门,写者走写锁的门
在这里插入图片描述

  • 读者申请锁 和 释放锁
    - 第一个读者:先申请读锁进入临界区,这时他要申请写锁(如果成功这时正式进入读模式),这样写者就就无法进入临界区达到读者写者互斥。但是读者走的时候要释放读锁,否则其他读者进不来
    - 中间的读者:除了申请写锁(如果申请写锁就会造成死锁),其他和上面一模一样
    - 最后一个读者:除了最后一步释放锁的时候要把写锁归还,其他和上面一样

  • 写者申请锁 和 释放锁:就是直接申请写锁,释放就是直接释放写锁。

思考:这样的设计有什么缺点?
这样的设计实际上是读者优先,读者只要申请到锁,写者必须等待所有读者读完,很容易造成饥饿问题!思路二就设计一个写者优先的读写锁。

模拟实现

代码仓库version1

#include <pthread.h>
// 使用两个mutex来实现读写锁
namespace sht
{
    class sht_rwlock
    {
    private:
        pthread_mutex_t m1; // 用来锁读者 目的是为了保护read_num这个临界资源
        pthread_mutex_t m2; // 用来锁写者 用来控制读者之间的互斥
        int read_num = 0;   // 记录读者的数量
        int cur_state = 0;
        // 0 : 表示没有上锁
        // 1 :当前在读模式
        // -1 :当前在写模式

    public:
        // 初始化
        int sht_rwlock_init()
        {

            pthread_mutex_init(&m1, nullptr);
            pthread_mutex_init(&m2, nullptr);
            return 0;
        }

        int sht_rwlock_destroy()
        {
            pthread_mutex_destroy(&m1);
            pthread_mutex_destroy(&m2);
        }

        int sht_rwlock_rdlock()
        {
            pthread_mutex_lock(&m1);
            read_num++;

            if (read_num == 1)
                pthread_mutex_lock(&m2);
            // 改变成读者状态必须一定要放在申请到读者锁之后!!!!!!!!
            cur_state = 1;

            // 读者之间不是互斥关系,所以要解锁,否则其他读者进不来
            pthread_mutex_unlock(&m1);
            return 0;
        }

        int sht_rwlock_tryrdlock()
        {
            int ret1 = pthread_mutex_trylock(&m1);
            if (ret1 != 0)
                return -1;

            if (read_num == 1)
            {
                int ret2 = pthread_mutex_trylock(&m2);
                if (ret2 != 0)
                    return -1;
            }
            read_num++;
            cur_state = 1;

            pthread_mutex_unlock(&m1);
            return 0;
        }

        int sht_rwlock_wrlock()
        {
            int ret = pthread_mutex_lock(&m2);
            // 如果能走到这一步代表:读者申请到锁了,而读者和所有人互斥,所以我们改变状态并不归还锁
            cur_state = -1;
            return ret;
        }

        int sht_rwlock_trywrlock()
        {
            int ret = pthread_mutex_lock(&m2);
            if (ret != 0)
                return -1;
            // 如果能走到这一步代表:读者申请到锁了,而读者和所有人互斥,所以我们改变状态并不归还锁
            cur_state = -1;
            return ret;
        }

        int sht_rwlock_unlock()
        {
            if (cur_state == -1)
            {
                // 写者还锁
                pthread_mutex_unlock(&m2);
                cur_state = 0;
            }
            else if (cur_state == 1)
            {
                // 读者还锁
                pthread_mutex_lock(&m1);

                if (read_num == 1)
                    pthread_mutex_unlock(&m2);

                read_num--;

                pthread_mutex_unlock(&m1);
            }
            else
                return -1;

            return 0;
        }
    };

} // namespace sht

思路2——两个条件变量一个锁(写者优先)

        pthread_cond_t reader_cond;  //读者队列
        pthread_cond_t writer_cond;   //写者队列
        pthread_mutex_t m;
        int reader_num; // 读者等待的数量
        int writer_num; // 写者等待的数量
        int cur_state;
        // 1 : 代表读者模式
        // 0 : 代表锁是为获取的
        // -1 :代表写者模式
  • 申请读锁的思路:
    在这里插入图片描述

  • 申请写锁的思路:

在这里插入图片描述


  • 释放锁的思路

在这里插入图片描述

模拟实现

代码仓库 version2

#include <pthread.h>

// 写者优先的锁
namespace sht
{
    class sht_rwlock
    {
    public:
        int sht_rwlock_init()
        {
            pthread_cond_init(&reader_cond, nullptr);
            pthread_cond_init(&writer_cond, nullptr);
            pthread_mutex_init(&m, nullptr);
        }

        int sht_rwlock_destroy()
        {
            pthread_cond_destroy(&reader_cond);
            pthread_cond_destroy(&writer_cond);
            pthread_mutex_destroy(&m);
        }

        int sht_rwlock_rdlock()
        {
            pthread_mutex_lock(&m);
            if (cur_state == 1)
            {
                // 当前在读模式,因为我们是写者优先所以我们看看是否有写者线程在等待
                // 如果有我们就先执行写者,让读者去排队
                // 如果没有,那么读锁就申请成功
                while (writer_num > 0)
                {
                    reader_num++;
                    pthread_cond_wait(&reader_cond, &m);
                    reader_num--;
                }
            }
            else if (cur_state == -1)
            {
                // 如果当前是写者模式,读者直接去排队
                reader_num++;
                pthread_cond_wait(&reader_cond, &m);
                reader_num--;
            }
            else
            {
                cur_state = 1;
            }

            pthread_mutex_unlock(&m);
        }

        int sht_rwlock_tryrdlock()
        {
            pthread_mutex_lock(&m);

            if (cur_state == 0 || cur_state == 1 && writer_num == 0)
            {
                pthread_mutex_unlock(&m);
                return 0;
            }
            return -1;
        }

        int sht_rwlock_wrlock()
        {
            pthread_mutex_lock(&m);
            if (cur_state == -1)
            {
                writer_num++;
                pthread_cond_wait(&writer_cond, &m);
                writer_num--;
            }
            else if (cur_state == 1)
            {
                writer_num++;
                pthread_cond_wait(&writer_cond, &m);
                writer_num--;
            }
            else
            {
                // 把状态改成写者模式
                cur_state = -1;
            }
            pthread_mutex_unlock(&m);
            return 1;
        }
        int sht_rwlock_trywrlock()
        {
            pthread_mutex_lock(&m);
            if (cur_state == -1)
            {
                pthread_mutex_unlock(&m);
                return 0;
            }
            return -1;
        }
        int sht_rwlock_unlock()
        {
            pthread_mutex_init(&m, nullptr);

            if (cur_state == 1)
            {

                if (writer_num > 0)
                {
                    cur_state = -1;
                    pthread_cond_signal(&writer_cond);
                }
                else
                {
                    if (reader_num > 0)
                    {
                        pthread_cond_signal(&reader_cond);
                    }
                    else
                        cur_state = 0; // 这一步一定不能丢,如果当前两个队列均为空时,要置为初始状态
                    // 不然线程就会卡住
                }
            }
            else if (cur_state == -1)
            {
                // 写者来还锁,如果写队列不为空 ,唤醒写队列中的写者(写者优先)
                // 如果写者队列空了,但是读者队列不为空,唤醒读者
                // 两个队列都为空,把状态置成初始状态

                if (writer_num > 0)
                {

                    pthread_cond_signal(&writer_cond);
                }
                else
                {
                    if (reader_num > 0)
                    {
                        cur_state = 1;
                        pthread_cond_signal(&reader_cond);
                    }
                    else
                        cur_state = 0;
                }
            }
            else
            {
                return -1;
            }

            pthread_mutex_unlock(&m);
        }

    private:
        pthread_cond_t reader_cond;
        pthread_cond_t writer_cond;
        pthread_mutex_t m;
        int reader_num; // 读者等待的数量
        int writer_num; // 写者等待的数量
        int cur_state;
        // 1 : 代表读者模式
        // 0 : 代表锁是为获取的
        // -1 :代表写者模式
    };
} // namespace sht

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/435553.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

d2l Nadaraya-Waston核回归

注意力机制里面的非参数注意力汇聚 目录 1.目标任务 2.数据生成 2.1构造原始数值 3.非参数注意力汇聚 4.对注意力机制的理解 1.目标任务 使用y_train(有噪声),拟合y_truth(没噪声)。给你所有的y_train&#xff0c;构造注意力权重生成拟合曲线。 2.数据生成 n_train 50…

五款高效易用的项目管理软件,提升团队工作效率

项目管理软件是为了协助团队或公司便捷和高效地完成工作任务和管理项目而专门设计的软件工具。有了它&#xff0c;团队成员可以共享资源&#xff0c;跟踪项目进度和成果&#xff0c;识别问题并及时解决。与传统的手工方式相比&#xff0c;项目管理软件可以提高工作效率和生产力…

Centos7上安装vscode和ssh

Centos7上安装vscode和ssh 一.前言二.Centos7上安装vscode三&#xff0c;Centos7上配ssh3.1 查看是否安装ssh环境3.2 配置ssh配置文件3.3 启动ssh服务 一.前言 在用linux环境编译项目的时候&#xff0c;比较习惯用ubuntu环境&#xff0c;而对centos环境的一些命令工具使用的比…

外链是什么意思,什么是外链

外链就是指在别的网站导入自己网站的链接。导入链接对于网站优化来说是非常重要的一个过程。导入链接的质量间接影响了我们的网站在搜索引擎中的权重。外链是互联网的血液&#xff0c;是链接的一种。没有链接的话&#xff0c;信息就是孤立的&#xff0c;结果就是我们什么都看不…

计算机网络笔记(方老师408课程)(持续更新)

文章目录 前言互联网概述互联网发展的三个阶段互联网标准化机构 互联网的组成边缘部分的通信方式核心部分的交换方式 我国计算机网络的发展计算机网络的类别计算机网络的性能速率、带宽、吞吐量时延时延带宽积往返时间RTT&#xff08;Round-Trip Time&#xff09;利用率非性能特…

SpringCloud分布式配置中心——Config

Config 本专栏学习内容来自尚硅谷周阳老师的视频 有兴趣的小伙伴可以点击视频地址观看 由于微服务越来越多&#xff0c;项目越来越庞大&#xff0c;每一个项目都至少有两三个不同环境的application.properties文件&#xff0c;不易管理&#xff0c;假设我们数据库迁移&#xff…

笔记--java sort() 方法排序

背景 最近在刷一道算法题 《字符串重新排序》时&#xff0c;发现自己有思路但是写代码的时候就无从下手了 而且看了答案之后还没看懂 关键就是基础不好 对于排序没有理解&#xff08;虽然我学过常用的排序算法 但是都是理念 实践少&#xff09; 目的 从实践和原理出发 重点是从…

参数处理、查询语句

一、Mybatis参数处理 1、数据准备 pojo类&#xff1a; public class Student {private Long id;private String name;private Integer age;private Double height;private Character sex;private Date birth;// constructor// setter and getter// toString }2、单个简单类型…

设计模式 -- 命令模式

前言 月是一轮明镜,晶莹剔透,代表着一张白纸(啥也不懂) 央是一片海洋,海乃百川,代表着一块海绵(吸纳万物) 泽是一柄利剑,千锤百炼,代表着千百锤炼(输入输出) 月央泽,学习的一种过程,从白纸->吸收各种知识->不断输入输出变成自己的内容 希望大家一起坚持这个过程,也同…

线性表详解

目录 1.线性表的定义和特点 2.案例 2.1一元多项式的计算 可以通过下面这个题目简单练习一下 2.2稀疏多项式的计算 2.3图书信息管理系统 3.线性表的类型定义 4.线性表的顺序表示和实现 4.1线性表的顺序储存表示 4.2顺序表中基本操作的实现 5.线性表的链式表现和实现 …

vba:inputbox

inputbox函数与方法 1.区别一&#xff1a;外观区别 InputBox 函数 在一对话框来中显示提示&#xff0c;等待用户输入正文或按下按钮&#xff0c;并返回包含文本框内容的 String。 Application.InputBox 方法 显示一个接收用户输入的对话框。返回此对话框中输入的信息。 -----…

分享一个国内使用的ChatGPT的方法

介绍 ChatGPT ChatGPT是一种基于自然语言处理技术的对话生成模型。它是由OpenAI公司开发的一种语言模型&#xff0c;可以在大规模语料库上进行无监督学习&#xff0c;并生成高质量的自然语言文本。ChatGPT可以用于多种应用场景&#xff0c;例如智能客服、语音助手、聊天机器人…

JAVA学习笔记(注解)

1. JDK预定义注解 (1) Deprecated&#xff08;表示标记对象已过时&#xff09; (2) SuppressWarnings("all") &#xff08;忽略标记对象的警告&#xff09; 2. 元注解&#xff08;用于描述注解的注解&#xff09; Target 描述注解所生效的位置 Retention 描述注…

SpringBooot

目录 一、简介 1、使用原因 2、JavaConfig &#xff08;1&#xff09;Configuration注解 &#xff08;2&#xff09;Bean注解 &#xff08;3&#xff09;ImportResource注解 &#xff08;4&#xff09;PropertyResource注解 &#xff08;5&#xff09;案例 3、简介 4…

Faster-RCNN代码解读8:代码调试与总结

Faster-RCNN代码解读8&#xff1a;代码调试与总结 前言 ​ 因为最近打算尝试一下Faster-RCNN的复现&#xff0c;不要多想&#xff0c;我还没有厉害到可以一个人复现所有代码。所以&#xff0c;是参考别人的代码&#xff0c;进行自己的解读。 ​ 代码来自于B站的UP主&#xff0…

网络协议-前端重点——DNS和CDN

目录 DNS的基础知识 统一资源定位符&#xff08;URL&#xff09;&#xff08;网址&#xff09; DNS&#xff08;Dimain Name System&#xff09;(域名系统) DNS Query过程 DNS记录 A记录 AAAA记录 CNAME记录&#xff08;Canonical Name Record&#xff09; MX记录&#…

Blender3.5 视图切换

目录 1. 数字小键盘切换视图1.1 正交顶视图1.2 正交前视图1.3 正交右视图1.4 透视图1.5 四格视图 2. 鼠标点击切换视图2.1 点击视图&#xff0c;根据需求选择对应视图2.2 点导航栏的坐标轴切换 3. 启用字母区数字键3.1 编辑——偏好设置——输入——勾选“模拟数字键” 1. 数字…

Linux驱动——高级I/O操作(四)

目录 几种I/O模型总结 异步通知 几种I/O模型总结 阻塞 IO:在资源不可用时&#xff0c;进程阻塞&#xff0c;阻塞发生在驱动中&#xff0c;资源可用后进程被唤醒,在阻塞期间不占用CPU&#xff0c;是最常用的一种方式。 非阻塞 I/O: 调用立即返回&#xff0c;即便是在资…

《Unity Shader 入门精要》第10章 高级纹理

第10章 高级纹理 10.1 立方体纹理 在图形学中&#xff0c;立方体纹理 &#xff08;Cubemap&#xff09; 是环境映射 &#xff08;Environment Mapping&#xff09; 的一种实现方法。 和之前见到的纹理不同&#xff0c;立方体纹理一共包含了6张图像&#xff0c;这些图像对应了…

typescript的keyof的用法

第一种&#xff1a;与接口一起用&#xff0c;返回联合类型 interface Person {name: string;age: number;location: string;}type K1keyof Person; // "name" | "age" | "gender" let a:K1name 第二种&#xff1a;与typeof一起用&#xff0c;可…