操作系统笔记-2.3.5.2 读者-写者问题

王道操作系统笔记,视频链接:2.3.5.2 读者-写者问题

问题描述

  1. 问题描述:
    • 有读者和写者两组并发进程,共享一个文件,
    • 当两个或两个以上的读进程同时访问共享数据时不会产生副作用,
    • 但若某个写进程和其他进程(读进程或写进程)同时访问共享数据时则可能导致数据不一致的错误。
    • 因此要求:
      • ①允许多个读者可以同时对文件执行读操作;
        • 与消费者进程不同,读者进程在读数据后并不会将数据清空,并不会改变数据,因此多个读者可同时访问共享数据
      • ②只允许一个写者往文件中写信息;
      • ③任一写者在完成写操作之前不允许其他读者或写者工作;
        • 读进程与写进程同时共享数据,可能导致读出的数据不一致的问题
        • 两个写进程同时共享数据,可能导致数据错误覆盖的问题
      • ④写者执行写操作前,应让已有的读者和写者全部退出。
  2. 问题分析:
    • 两类进程:写进程、读进程
    • 互斥关系:写进程---写进程、写进程---读进程。读进程与读进程不存在互斥问题。
  3. 第一步实现:
cpp 复制代码
semaphore rw=1;      //用于实现对共享文件的互斥访问
int count = 0;       //记录当前有几个读进程在访问文件
writer (){
    while(1){
        P(rw);        //写之前"加锁"
        写文件...
        V(rw);        //写完了"解锁"
    }
}
reader (){
    while(1){
        if (count==0)      //由第一个读进程负责
            P(rw);         //读之前"加锁"
        count++;           //访问文件的读进程数+1
        读文件...
        count--;           //访问文件的读进程数-1
        if (count==0)      //由最后一个读进程负责
            V(rw);         //读完了"解锁"
    }
}
// 该代码实现了多个读者同时读文件,
// 但是由于if语句与P、V操作无法一气呵成,
// 所以可能导致多个读进程同时认为自己是第一个读进程,从而"加锁"
// 于是我们引入mutex,用于对count变量的互斥
  1. 第二步实现:
cpp 复制代码
semaphore rw=1;      //用于实现对共享文件的互斥访问
int count = 0;       //记录当前有几个读进程在访问文件
semaphore mutex = 1; //用于保证对count变量的互斥访问
writer (){
    while(1){
        P(rw);        //写之前"加锁"
        写文件...
        V(rw);        //写完了"解锁"
    }
}
reader (){
    while(1){
        P(mutex);          //各读进程互斥访问count
        if (count==0)      //由第一个读进程负责
            P(rw);         //读之前"加锁"
        count++;           //访问文件的读进程数+1
        V(mutex);
        读文件...
        P(mutex);          //各读进程互斥访问count
        count--;           //访问文件的读进程数-1
        if (count==0)      //由最后一个读进程负责
            V(rw);         //读完了"解锁"
        V(mutex);
    }
}
// 该代码解决了多个读进程冲突访问count的情况,逻辑上可行
// 但是依然存在潜在的问题:
// 只要有读进程还在读,写进程就要一直阻塞等待,可能"饿死"。
// 因此,这种算法中,读进程是优先的
// 所以,我们引入另一个信号量w,用于实现"写优先"
  1. 第三步实现:
cpp 复制代码
semaphore rw=1;      //用于实现对共享文件的互斥访问
int count = 0;       //记录当前有几个读进程在访问文件
semaphore mutex = 1; //用于保证对count变量的互斥访问
semaphore w = 1;     //用于实现"写优先"
writer () {
    while(1) {
        P(w);
        P(rw);
        写文件...
        V(rw);
        V(w);
    }
}
reader () {
    while(1) {
        P(w);
        P(mutex);
        if(count==0)
            P(rw);
        count++;
        V(mutex);
        V(w);          // 读进程在真正读文件前释放w,使得后面的读进程可以继续进入
        读文件...
        P(mutex);
        count--;
        if(count==0)
            V(rw);
        V(mutex);
    }
}
  1. 关于第三步实现的几种情况:
    • 分析以下并发执行 P(w) 的情况:(前几个略,可以自己慢慢分析)
    • 读者1→读者2
    • 写者1→写者2
    • 写者1→读者1
    • 读者1→写者1→读者2
    • 写者1→读者1→写者2
      • 因为读写都以P(w)开头,所以写者1没运行完之前,读者1、写者2都会被阻塞在P(w)
      • 因为记录型信息量会有一个阻塞队列,所以实际上写者1运行完后,会先唤醒读者1
      • 结论:
        • 在这种算法中,连续进入的多个读者可以同时读文件;
        • 写者和其他进程不能同时访问文件;
        • 写者不会饥饿 ,但也并不是真正的"写优先" ,而是相对公平的先来先服务原则。
        • 有的书上把这种算法称为"读写公平法"
  2. 真正的"写优先"算法:
    • 这里只是举个例子,写优先算法不止这一个。
cpp 复制代码
semaphore rw=1;      //用于实现对共享文件的互斥访问
int count = 0;       //记录当前有几个读进程在访问文件
semaphore mutex = 1; //用于保证对count变量的互斥访问
semaphore w = 1;     //用于实现"写优先"的门控信号量

int write_count = 0;      // [新增] 记录当前等待或正在写的写进程数量
semaphore w_mutex = 1;    // [新增] 用于保护 write_count 变量的互斥访问

writer () {
    while(1) {
        P(w_mutex);                      // [新增] 互斥访问 write_count
        // [修改] 只有第一个写进程才执行 P(w) 关门,阻止新读者
        if (write_count == 0)            
            P(w);
        write_count++;                   // [新增] 写进程数量+1
        V(w_mutex);                      // [新增]
        P(rw);
        写文件...
        V(rw);
        P(w_mutex);                      // [新增] 互斥访问 write_count
        write_count--;                   // [新增] 写进程数量-1
        // [修改] 只有当最后一个写进程离开时,才执行 V(w) 开门,允许读者进入
        if (write_count == 0)            
            V(w);
        V(w_mutex);                      // [新增]
    }
}

reader () {
    while(1) {
        P(w);
        P(mutex);
        if(count==0)
            P(rw);
        count++;
        V(mutex);
        V(w);
        读文件...
        P(mutex);
        count--;
        if(count==0)
            V(rw);
        V(mutex);
    }
}
// 其实和读优先差不多,额外设置了write_count和w_mutex
// 用于控制让第一个写进程上锁,
// 最后一个写进程释放锁,
// 所以这种方法写进程一直到来时,会导致读进程"饥饿"
// 是真正的"写优先"

知识回顾与重要考点

  1. 读者-写者问题为我们解决复杂的互斥问题提供了一个参考思路:
    • 其核心思想在于设置了一个计数器 count 用来记录当前正在访问共享文件的读进程数。
    • 我们可以用 count 的值 来判断当前进入的进程是否是第一个/最后一个读进程,从而做出不同的处理。
    • 另外,对 count 变量的检查和赋值不能一气呵成导致了一些错误,如果需要实现"一气呵成" ,自然应该想到用互斥信号量 。
      • 这里的"一气呵成"和原语的开关中断不同,开关中断不会切换进程,这个会,只是保证了同一时间段只有一个进程能修改访问某变量。
    • 最后,还要认真体会我们是如何解决"写进程饥饿"问题的。
    • 绝大多数的考研PV操作大题都可以用之前介绍的几种生产者---消费者问题的思想来解决,
    • 如果遇到更复杂的问题,可以想想能否用读者---写者问题的这几个思想来解决。
相关推荐
无心水1 小时前
20、大数据架构设计理论与实践 核心考点与学习笔记
大数据·笔记·学习·架构师·系统架构设计师·软考系统架构设计师·架构设计师
snysy2 小时前
WordPress伪静态404故障排查笔记
android·笔记·wordpress
Z21-X2 小时前
云原生运维实战笔记:华为云计算与网络类服务全景解析
运维·笔记·学习·云原生·华为云
半壶清水2 小时前
[软考网规考点笔记]-局域网之冗余网关技术
网络·笔记·智能路由器
LK_072 小时前
【Agent】工具调用的安全边界:从模型输出到程序执行
人工智能·笔记·python·agent
罗四强2 小时前
第10篇:打包上架:签名、打包 HAP、发布到华为应用市场
分布式·操作系统·harmonyos·arkts·鸿蒙系统
乐橙开放平台2 小时前
跨地域监控难管理?通过 GB28181 把各地海康大华乐橙摄像头注册到云端平台
开发语言·笔记·物联网·自动化·php·音视频·智能家居
动词ing2 小时前
【学习笔记】51单片机 模块化编程
笔记·学习·51单片机
海绵宝宝转agent2 小时前
计网基础知识
网络·笔记·学习·智能路由器
ACP广源盛139246256733 小时前
Type‑C 扩展坞选型笔记:USB3.0 双通道信号切换方案 LH3412@ACP 评估
笔记·硬件架构·硬件工程·国产芯片·开关