【Linux】读者写者问题与读写锁

news2024/12/24 22:08:46

在这里插入图片描述

终此一生,只有两种办法:
要么梦见生活,要么落实生活。
--- 勒内・夏尔 ---

读写锁和自旋锁

  • 1 读者写者问题
  • 2 读写锁
  • 3 读写锁的两大特性

1 读者写者问题

读者写者是一种生产消费模型,所以就满足"321"原则:

  1. 三种关系:生产与消费,生产与生产,消费与消费
  2. 两种角色:生产者与消费者
  3. 一个交易场所:临界资源

在读者写者问题中,读者与读者是并发的,不同读者之间不会互相影响,因为只是访问数据,并不会读数据进行修改。写者与写者是互斥的,临界资源只能让一个写者进行书写。读者与写者的关系比较复杂,是互斥与同步,读写不能同时进行,读完了要与写进行同步,写完了要与读同步。

一般而言:读者写者模型中读者很多,写者很少。

2 读写锁

读写锁的逻辑可以这么理解:

  1. 首先需要一个互斥锁,来对写者进行上锁。保证写者与写者之间的互斥关系
  2. 然后对应读者来说,他们是并发执行的,为了可以保证读完了可以进行写的同步,需要一个计数器来记录读者的数量。
  3. 有了这个计数器,那么就相当于读者都会访问这个计数器,所以需要锁来进行保护。
  4. 当进入读者时,先将将计数器锁获取。然后在对计数器进行++,再进行解锁,然后,写锁获取,让写者无法获取锁阻塞 ,进行读操作。之后在将计数器锁获取进行–,再进行解锁
  5. 当进入写者时,将写者锁获取,之后进行写操作,最后进行解锁。

这是读写锁的逻辑,当实际中线程库为我们提供了专门的读写锁,我们不需要使用互斥锁来进行模拟!

#include <pthread.h>
//销毁
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
//初始化
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
const pthread_rwlockattr_t *restrict attr);
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
//读者锁
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 <pthread.h>
#include <iostream>
#include <vector>
#include <stdlib.h>
#include <unistd.h>

// 进行访问的全局数据
int Data = 0;
// 读写锁
pthread_rwlock_t rwlock;
// 读操作
void *read(void *args)
{
    int id = *(int *)args;
    //sleep(1);
    while (true)
    {
        // 读者上锁
        pthread_rwlock_rdlock(&rwlock);
        // 进行读操作
        std::cout << "读者线程-" << id << "正在读取数据:" << Data << std::endl;
        sleep(1);
        // 完成写操作
        // 解锁
        pthread_rwlock_unlock(&rwlock);
    }
    delete (int*)args;
    return nullptr;
}
// 写操作
void *write(void *args)
{
    int id = *(int *)args;
    while (true)
    {
        // 读者锁
        pthread_rwlock_wrlock(&rwlock);
        // 写操作
        Data = rand() % 100;
        std::cout << "写者线程-" << id << "正在写入数据:" << Data << std::endl;
        sleep(1);
        // 解锁
        pthread_rwlock_unlock(&rwlock);
    }
    delete (int*)args;
    return nullptr;
}

int main()
{
    // 读者写者数量
    int write_count = 2;
    int read_count = 2;

    std::vector<pthread_t> wthreads(write_count, 0);
    std::vector<pthread_t> rthreads(read_count, 0);

    pthread_rwlock_init(&rwlock, nullptr);
    srand(time(nullptr));

    for (int i = 0; i < read_count; i++)
    {
        int *id = new int(i);
        pthread_create(&rthreads[i], nullptr, read, id);
    }
    for (int i = 0; i < write_count; i++)
    {
        int *id = new int(i);
        pthread_create(&wthreads[i], nullptr, write, id);
    }

    for (int i = 0; i < read_count; i++)
    {

        pthread_join(rthreads[i], nullptr);
    }
    for (int i = 0; i < write_count; i++)
    {
        pthread_join(wthreads[i], nullptr);
    }

    pthread_rwlock_destroy(&rwlock);

    return 0;
}

运行会发现:
在这里插入图片描述
写者根本进不来,只有读者在进行,这是因为这里读者读到数据没有进行处理,而是连续的再进行读取,这就导致写者没有机会获取到全局变量,就不能进行写操作。我们可以加入sleep(1)模拟处理数据:这样写者就有机会获取到全局变量进行处理了!!!
在这里插入图片描述

3 读写锁的两大特性

在生产者消费者模型中,消费者与生产者的关系是对等的。但在读者写者问题中,读者与写者的关系不对等。一般会有两种策略:

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

读者优先的实际应用场景:

  • 文档数据库:
    在文档数据库中,通常读取操作远多于写入操作。采用读者优先策略可以最大化读取效率,让多个用户同时读取文档而不会相互阻塞。例如,一个在线百科全书网站,用户频繁读取词条内容,但编辑更新的频率相对较低。
  • 配置文件读取:
    在多线程应用中,配置文件通常会被频繁读取但很少写入。使用读者优先的读写锁可以保证配置文件在更新时不会影响大量读取操作。
  • 缓存系统:
    缓存系统中的数据读取非常频繁,而写入(缓存失效或更新)相对较少。读者优先策略可以保证缓存数据的快速访问。

其潜在问题就是会造成写者饥饿:如果写者操作不频繁,但读者操作非常频繁,写者可能长时间无法获得锁,导致写入操作被无限期延迟。

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

写者优先的实际应用场景:

  • 实时数据系统:
    在需要实时更新和读取数据的应用中,写者优先策略可以确保数据的实时性。例如,股票市场信息需要实时更新,并且更新必须尽快反映给所有用户。
  • 状态更新:
    在某些系统中,状态的更新(写入操作)需要被尽快处理以保证系统的正确性和一致性。例如,游戏状态更新需要及时反映给所有玩家。
  • 日志系统:
    在日志系统中,写入操作是连续的,且重要性高于读取操作。写者优先可以确保日志记录不会因为读取操作而延迟。

写者优先的潜在问题是会造成读者饥饿:如果写者操作非常频繁,读者可能会长时间无法获得锁,导致读取操作被阻塞。

总之,读者优先适合读取操作远多于写入操作的场景,可以最大化读取效率。写者优先适合写入操作的重要性高于读取操作的场景,可以确保写入操作的及时性

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

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

相关文章

Node.js和vue3实现GitHub OAuth第三方登录

Node.js和vue3实现GitHub OAuth第三方登录 前言 第三方登入太常见了&#xff0c;微信&#xff0c;微博&#xff0c;QQ…总有一个你用过。 在开发中&#xff0c;我们希望用户可以通过GitHub账号登录我们的网站&#xff0c;这样用户就不需要注册账号&#xff0c;直接通过GitHu…

mysql树形结构返回是否叶子节点

我们界面上展示树形结构的时候往往会用到懒加载&#xff0c;做懒加载需要知道哪个节点是叶子节点&#xff0c;这样叶子节点就不需要继续往下加载了&#xff0c;这种需求可以通过sql实现 先来看下表结构 方式一,通过sql语句直接获取leaf 什么是叶子节点&#xff1f;就是没有哪…

海外云服务器安装 Redis 6.2.x (Ubuntu 18.04 记录篇三)

本文已首发于 秋码记录 通过前两篇的实践&#xff0c;我们已然在海外云服务器/VPS安装了JDK和MariaDB数据库&#xff0c;一个能够运行Java项目的海外云服务器/VPS算是告一段落了。 然而&#xff0c;在这请求量与日俱增的情况下&#xff0c;MariaDB数据库显然是在超负债的工作…

《黑暗之魂2:原罪学者》是什么类型的游戏 《黑暗之魂》可以在苹果Mac电脑上玩吗?

在宏大的世界观游戏中&#xff0c;《黑暗之魂2:原罪学者》脱颖而出&#xff0c;以其探索性和挑战性征服了全球玩家的心灵。下面我们来看看《黑暗之魂2:原罪学者》是什么类型的游戏&#xff0c;《黑暗之魂2:原罪学者》可以在苹果电脑玩吗的相关内容。 一、《黑暗之魂2:原罪学者》…

[计算机网络]-计网学习笔记-计网知识点总结(附完整笔记)

本笔记是跟着 b站 湖科大教书匠 视频做的笔记&#xff0c;其中图片为视频中的 PPT&#xff0c;加上了自己的注释。 这是原视频链接。大家可以参照着笔记看原视频。视频中的 PPT 做的非常好。 【计算机网络微课堂&#xff08;有字幕无背景音乐版&#xff09;】https://www.bilib…

TOPSIS法详细讲解+Python代码实现

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

数据结构——双链表实现和注释浅解

关于双链表的基础部分增删查改的实现和一点理解&#xff0c;写在注释里~ 前言 浅记 1. 哨兵位的节点不能被删除&#xff0c;节点的地址也不能发生改变&#xff0c;所以是传一级指针 2. 哨兵位并不存储有效数据&#xff0c;所以它并不是有效节点 3. 双向链表为空时&#xff…

单点登录:cas单点登录实现原理浅析

cas单点登录实现原理浅析 一晃几个月没写博客了&#xff0c;今年多灾多难的一年。 安能摧眉折腰事权贵&#xff0c;使我不得开心颜&#xff01; 财富是对认知的补偿&#xff0c;不是对勤奋的嘉奖。勤奋只能解决温饱&#xff0c;要挣到钱就得预知风口&#xff0c;或者有独到见解…

探寻 IP 代理地址繁多之因

在当今的网络天地里&#xff0c;IP 代理服务随处可见&#xff0c;且令人称奇的是&#xff0c;它们常常手握海量的 IP 地址可供挑选。那么&#xff0c;究竟是什么原因使得 IP 代理拥有如此众多的地址呢&#xff1f;现在&#xff0c;就让我们一同深入探究这个神秘现象背后的缘由。…

Camunda调用子流程案例

调用子流程 调用子流程是指子流程在主流程的外面。子流程一般是多个流程可重用的流程&#xff0c;也可以独立调用子流程。 可以对比编程中的方法抽取。子流程运行时&#xff0c;主流程也是等待状态。子流程结束&#xff0c;主流程继续。 BPMN设计 主流程 全局配置 上传视频 处…

并查集基础与简单扩展应用

并查集 基础题目路径压缩 扩展应用扩展题目1扩展题目2 并查集的结构是一棵树 并查集有两种功能&#xff0c;一种是判断两个元素是否在同一集合&#xff0c;第二种是合并两个集合 并查集的实现需要记录每个节点的父亲节点 判断两个元素是否在同一集合&#xff0c;即判断两个元…

ARM编程模型、指令集、ARM汇编语言程序设计

一、编程模型 1.1数据和指令类型 在之后的演示当中&#xff0c;我们大多数将采用ARM指令集 1.2处理器工作模式 1.3ARM寄存器 1.3.1分类 &#xff08;1&#xff09; 31 个通用寄存器&#xff0c;包括 PC&#xff08;程序计数器&#xff09;在内&#xff0c;都是 32 位的寄存器…

实习项目|苍穹外卖|day7

缓存菜品 1.根据原型进行需求分析与设计&#xff08;接口文档&#xff09; 2.根据接口设计DTO&#xff08;redis数据类型选取&#xff09; 3.编码controller-》service-》mapper GetMapping("/list")ApiOperation("根据分类id查询菜品")public Result<…

51单片机-第十三节-直流电机驱动(PWM)

一、直流电机介绍&#xff1a; 直流电机是一种将电能转换为机械能的装置。 一般的直流电机有两个电极&#xff0c;电极正接&#xff0c;电机正转&#xff0c;电极反接&#xff0c;电机反转。 直流电机主要由永磁体&#xff08;定子&#xff09;、线圈&#xff08;转子&#…

GB35114 USC安防平台 中星微国密摄像机配置 流程

中星微国密摄像机配置介绍 如下以中星微VS-IPC8021S-Y-T4摄像机为例&#xff0c;需要先各自获取p10文件&#xff0c;并通过证书签发机构或者测试SM2证书签发获取证书。 网络配置如下: 摄像机的IP地址为192.168.1.108&#xff0c;国标ID为34020000001320000015 系统的IP地址…

Robotframework框架基础

1.Robot Framework是开源的自动化测试框架&#xff0c;基于关键字驱动的测试方法2.它提供用于创建和执行自动化测试的工具和库&#xff0c;并支持使用不同的测试库和插件进行扩展 一.以下是Robot Framework框架的基础知识 1. 安装&#xff1a;通过pip安装Robot Framework和相…

打造可视化数字大屏供应链管理平台详解:从食堂采购系统源码开始

这篇文章将深入探讨直播美颜SDK与主播美颜工具的技术原理及其发展趋势&#xff0c;帮助开发者了解如何通过技术手段实现流畅、高效的实时美颜效果。 一、什么是直播美颜SDK&#xff1f; 直播美颜SDK是一种为开发者提供实时美颜功能的集成开发工具包。它通过对摄像头捕捉到的画…

现代计算机中数字的表示与浮点数、定点数

现代计算机中数字的表示与浮点数、定点数 导读&#xff1a;浮点数运算是一个非常有技术含量的话题&#xff0c;不太容易掌握。许多程序员都不清楚使用操作符比较float/double类型的话到底出现什么问题。这篇文章讲述了浮点数的来龙去脉&#xff0c;所有的软件开发人员都应该读…

轻松搞定用户认证:微搭低代码平台打造完美登录体验01用户登录

目录 1 创建数据源2 搭建后端API3 用户登录4 最终的代码总结 欢迎阅读我们的微搭低代码全栈开发课程&#xff0c;这是我们的第二篇。在第一篇中我们整体描述了小程序的功能结构&#xff0c;这一篇我们就进入实际的开发。 在开发小程序的时候&#xff0c;第一个需要考虑的就是用…

数据结构基础讲解(一)——线性表之顺序表专项练习

本文数据结构讲解参考书目&#xff1a; 通过网盘分享的文件&#xff1a;数据结构 C语言版.pdf 链接: https://pan.baidu.com/s/159y_QTbXqpMhNCNP_Fls9g?pwdze8e 提取码: ze8e 目录 前言 一.线性表的定义 二.线性表的基本操作 三.线性表的顺序存储和表示 四.顺序表中基本操作…