OS复习笔记ch5-4-2

2024-05-14 02:12
文章标签 笔记 复习 os ch5

本文主要是介绍OS复习笔记ch5-4-2,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

引言

承接上文我们介绍了信号量机制和应用信号量机制实现的进程同步和互斥,这一节我们将围绕一些经典问题对信号量机制展开更深入地探讨。

读者/写者问题

ppt

读者/写者问题与我们之前遇到的问题类型不同,它描述的是:

有读者和写者两组进程,它们共同访问同一个文件。

对于读者,它可以与多个读者共同读取文件(因为不会修改到文件);

对于写者,它不能与其他任何进程共同访问文件(如果另一进程是写,则可能覆盖同一内容;如果是读,则可能修改正在读的内容)。

这里的互斥问题是读写和写写互斥的问题,但与之前不同的是,除了实现读写和写写的互斥,我们还要实现读读的“不互斥”,即满足”读写互斥,写写互斥,读读允许“

Version1

首先准备一个信号量 rw = 1 表示当前是否有进程在访问文件(注意一开始是没有这样的进程的,1 表示的不是进程数目,是使用互斥信号量给定的初始值,代表任何时刻只有一个进程可以访问)。

在不考虑“读读不互斥”的情况下,我们的伪代码是这样的:

Writer(){               Reader(){while(1){             while(1){P(rw)                P(rw)写文件               读文件  V(rw)                V(rw)}                     }
}                      }

这个代码可以实现读写互斥和写写互斥,但显然无法实现“读读不互斥”,因为每个读进程之间也会受到 rw 的影响,使得某一时刻只能有一个读进程访问文件。

Version2

我们考虑从读进程入手,做一些改进。这里读和读不能同时进行的本质原因在于,所有的读进程都会经历“检查并上锁”这个步骤,而一个读进程进入后就会马上检查并上锁,导致另一个也想要进入的读进程被阻塞,所以我们考虑:能不能不要让所有的读进程都经历“检查并上锁”这一步骤?也就是说,某些进程可以跳过 P 操作,直接进入临界区,这样一来,这些进程就不存在在 P 操作这里被阻塞的可能性。

什么样的进程可以跳过 P 操作呢?


就是中间的那些读进程。因为一开始肯定要有读进程上锁、最后肯定要有读进程解锁,所以上锁和解锁的任务交付给第一个和最后一个进程,而中间的那些进程来去自如,只需要负责读文件,不需要参与上锁和解锁。
为了区分读进程的次序,我们准备一个 count = 0 的变量,它表示的是当前有多少个读进程正在访问文件。然后在读文件的前后,我们分别对 count 进行加一和减一的操作,每次读文件开始之前 count 会加一,所以在此之前如果变量为 0 ,说明当前读进程是第一个读进程;同理,每次读文件之后 count 会减一,所以在此之后如果变量为 0 ,说明当前读进程是最后一个读进程;

此时伪代码如下:

Reader(){while(1){if(count==0)P(rw)count++读文件count--if(count==0)V(rw)}
}

但是这样会产生一些问题。比方 1 号读进程首先进入并上锁,然后在 P 操作之后、count 加一变成 1 之前,进程切换到 2 号读进程,那么 2 号读进程就会卡在 P 操作这个地方,陷入阻塞,显然这时候无法实现我们想要的“读读不互斥”;又比方说,1 号读进程在 count 减一变成 0 之后、释放 rw 之前,进程切换到了 2 号读进程,那么 2 号同样又会被卡在 P 操作这里。所以我们还要进行改进。

问题其实就出在,对 count 的检查和赋值不是一个原子操作,这导致的结果是,如果在检查和赋值之间的空隙,进程发生切换,则必然会使得另一进程陷入阻塞。那么能不能让这两个操作一气呵成呢?事实上,可以把 count 当作是一个互斥访问的资源,对 count 的访问是互斥的,也就说明一个时间段内只能有一个读进程去访问它,即使这个过程中切换到了其它进程,那个进程也会被阻塞,从而保证只有一个进程可以访问 count,而这个访问就是检查和赋值,这种情况下,检查和赋值一定是不会被中断的。

准备一个互斥信号量 mutex = 1 表示对 count 的互斥访问,将检查和赋值封装在一个 PV 操作里。伪代码如下:

Reader(){while(1){P(mutex)if(count==0)P(rw)count++V(mutex)读文件P(mutex)count--if(count==0)V(rw)V(mutex)}
}

现在我们再来跑一下过程。
假设还是 1 号读进程运行到 P 操作的时候,进程切换到了 2 号读进程,那么由于互斥信号量 mutex 的存在,导致 2 号进程进入了 mutex 对应的阻塞队列 —— 是的,这时候看起来 2 号进程还是被阻塞了,不过我们要关注到的是,阻塞它的信号量是 mutex,不是 rw。这意味着,在进程重新切换回 1 号进程的时候,1 号进程一旦执行了 V(mutex),就可以将 2 号进程唤醒并送到就绪队列了。也就是说,尽管 2 号进程还是经历了“阻塞”这个过程,但是这个过程只是为了确保 1 号进程检查和上锁两个操作的原子性,一旦操作完成,2 号进程马上就被唤醒了。而之前那种情况不同,之前的情况是,导致 2 号进程被阻塞的是信号量 rw,除非 1 号进程读完后释放,否则 2 号进程会一直处于阻塞状态。这就是说,2 号进程永远不可能与 1 号进程同时读文件,但是改进后是可以的。

但事实上还有另一个问题更加严重——“读写不公平”。也就是说,这样的代码本质上是对读进程更有利的。


因为对读进程来说,一旦第一个读进程进来了,中间即使穿插再多的读进程,也都是允许的,他们根本不受到 rw 这个“锁”的限制;而对于写进程,它的运气就没这么好了,写进程只要想进来,就必须通过 rw 这个“锁”,而这个“锁” 实际上又掌握在最后一个读进程手里 —— 这就是说,万一读进程源源不断进来,始终轮不到最后一个读进程解锁,那么写进程就只能陷入无尽的等待了。

Version3

既然 rw 这把锁无法做到公正对待每一个进程,那我们就考虑在外层加一把“更大、更公正的锁”——所有的进程,无论读还是写,无一例外必须先通过这把“锁”的检查。为此,我们准备一个新的互斥信号量 w = 1,并将 Writer 和 Reader 的一些关键操作封装在 w 的一对 PV 操作里。此时,伪代码如下:

//用于实现对共享文件的互斥访问
semaphore rw = 1; //记录当前有几个读进程在访问文件
semaphore mutex = 1;//用于保证对count变量的互斥访问
semaphore w = 1//用于实现写优先
int count = 0; //用于记录读进程的次序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);读文件P(mutex); count--;if(count==0)	V(rw);V(mutex);}
}

我们来跑一下流程。
假设首先来到 1 号读进程,那么它就会执行 P 操作上锁,这个过程中即使有写进程想进来,也会被送到 w 对应的阻塞队列。在 1 号读进程执行到 V 操作之后,写进程才会被唤醒并送到就绪队列,之后就轮到写进程执行了,而写进程虽然通过第一个 P 操作,但是被卡在了第二个 P 操作(读进程尚未释放 rw),所以他来到了 rw 对应的阻塞队列。

注意!重点来了,如果这时候 2 号读进程也想要访问文件,那么在以前,它是不需要通过任何检查就可以直接来读文件的,并且直到 2 号读进程释放 rw 之后,写进程才能真正来执行写文件的操作。但是现在由于我们加了一把“更大的锁w,导致 2 号进程必须先通过 w 的检查,而由于写进程抢先在他之前上了锁,所以 2 号读进程被送到了 w 对应的阻塞队列。也就是说,现在的情况是:写进程等着 1 号读进程释放 rw,而 2 号读进程等着写进程释放 w,1 号读进程是让一切正常进行下去的关键。在处理机又来到 1 号读进程并执行 V(rw) 之后,写进程从 rw 的阻塞队列被唤醒,继续往下执行写文件的操作。而在写进程真正执行完之后,w 才能得到释放,由此又唤醒了 w 阻塞队列中的 2 号读进程,2 号读进程来到处理机运行。

如果换一种情况,是按照 写者 — 读者 — 写者的顺序,那么由于读者在第二个写者之前,所以是读者作为阻塞队列队头,第二个写者则次之,在后续执行过程中,根据队列“先进先出”的原则,也会是读者先于第二个写者访问文件。

综上,这种情况下,实际上谁先到、谁就在后续过程中先被执行(而不是像之前,无论写进程先还是后,读进程都可以“无视规则”抢先一步执行)。由此,我们就实现了“读写公平”。

总结

上文根据王道课上给的案例进行的分析,博主自身在理解这里的时候感觉不是很好记忆,自己结合老师上课的讨论摸索了一套方法解决这个问题。(信号量命名和王道不一致)

首先是解决读者优先问题,设置一个balance信号量(也有写写、写读互斥的作用),解决读写互斥放一个mutex信号量(读的时候不能写),读读允许同样设置一个count计数,配套设置cmutex解决count计数的原子操作。

具体实现思路如下

  1. 先设置好信号量初值
semaphore mutex = 1;
semaphore cmutex = 1;
semaphore balance = 1;
int count = 0;
  1. 然后对写者进程进行分析
writer() {while(1) {P(balance); // 读写平衡P(mutex); // 读写互斥写文件...P(mutex); V(balance); }
}
  1. 对于读者进程进行分析
reader() {while(1) {P(balance); // 读写平衡P(cmutex);  // count操作互斥if(count == 0) P(mutex); //读写互斥count--;V(cmutex);V(balance);读文件...P(cmutex); // count操作互斥count--;if(count == 0) V(mutex);V(cmutex);}
}
  • 读写互斥→mutex(斥)
  • 读读允许→cmutex、count(允)
  • 解决读者优先→balance(平衡)

由于读者写者问题确实有他自身的复杂性,所以一定要有自己的理解,不然很容易就不记得该如何解决,按照演绎的顺序就是三个信号量(一斥二允三平衡)+一个计数器count。

这篇关于OS复习笔记ch5-4-2的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/987458

相关文章

【学习笔记】 陈强-机器学习-Python-Ch15 人工神经网络(1)sklearn

系列文章目录 监督学习:参数方法 【学习笔记】 陈强-机器学习-Python-Ch4 线性回归 【学习笔记】 陈强-机器学习-Python-Ch5 逻辑回归 【课后题练习】 陈强-机器学习-Python-Ch5 逻辑回归(SAheart.csv) 【学习笔记】 陈强-机器学习-Python-Ch6 多项逻辑回归 【学习笔记 及 课后题练习】 陈强-机器学习-Python-Ch7 判别分析 【学

系统架构师考试学习笔记第三篇——架构设计高级知识(20)通信系统架构设计理论与实践

本章知识考点:         第20课时主要学习通信系统架构设计的理论和工作中的实践。根据新版考试大纲,本课时知识点会涉及案例分析题(25分),而在历年考试中,案例题对该部分内容的考查并不多,虽在综合知识选择题目中经常考查,但分值也不高。本课时内容侧重于对知识点的记忆和理解,按照以往的出题规律,通信系统架构设计基础知识点多来源于教材内的基础网络设备、网络架构和教材外最新时事热点技术。本课时知识

论文阅读笔记: Segment Anything

文章目录 Segment Anything摘要引言任务模型数据引擎数据集负责任的人工智能 Segment Anything Model图像编码器提示编码器mask解码器解决歧义损失和训练 Segment Anything 论文地址: https://arxiv.org/abs/2304.02643 代码地址:https://github.com/facebookresear

数学建模笔记—— 非线性规划

数学建模笔记—— 非线性规划 非线性规划1. 模型原理1.1 非线性规划的标准型1.2 非线性规划求解的Matlab函数 2. 典型例题3. matlab代码求解3.1 例1 一个简单示例3.2 例2 选址问题1. 第一问 线性规划2. 第二问 非线性规划 非线性规划 非线性规划是一种求解目标函数或约束条件中有一个或几个非线性函数的最优化问题的方法。运筹学的一个重要分支。2

【C++学习笔记 20】C++中的智能指针

智能指针的功能 在上一篇笔记提到了在栈和堆上创建变量的区别,使用new关键字创建变量时,需要搭配delete关键字销毁变量。而智能指针的作用就是调用new分配内存时,不必自己去调用delete,甚至不用调用new。 智能指针实际上就是对原始指针的包装。 unique_ptr 最简单的智能指针,是一种作用域指针,意思是当指针超出该作用域时,会自动调用delete。它名为unique的原因是这个

查看提交历史 —— Git 学习笔记 11

查看提交历史 查看提交历史 不带任何选项的git log-p选项--stat 选项--pretty=oneline选项--pretty=format选项git log常用选项列表参考资料 在提交了若干更新,又或者克隆了某个项目之后,你也许想回顾下提交历史。 完成这个任务最简单而又有效的 工具是 git log 命令。 接下来的例子会用一个用于演示的 simplegit

记录每次更新到仓库 —— Git 学习笔记 10

记录每次更新到仓库 文章目录 文件的状态三个区域检查当前文件状态跟踪新文件取消跟踪(un-tracking)文件重新跟踪(re-tracking)文件暂存已修改文件忽略某些文件查看已暂存和未暂存的修改提交更新跳过暂存区删除文件移动文件参考资料 咱们接着很多天以前的 取得Git仓库 这篇文章继续说。 文件的状态 不管是通过哪种方法,现在我们已经有了一个仓库,并从这个仓

忽略某些文件 —— Git 学习笔记 05

忽略某些文件 忽略某些文件 通过.gitignore文件其他规则源如何选择规则源参考资料 对于某些文件,我们不希望把它们纳入 Git 的管理,也不希望它们总出现在未跟踪文件列表。通常它们都是些自动生成的文件,比如日志文件、编译过程中创建的临时文件等。 通过.gitignore文件 假设我们要忽略 lib.a 文件,那我们可以在 lib.a 所在目录下创建一个名为 .gi

取得 Git 仓库 —— Git 学习笔记 04

取得 Git 仓库 —— Git 学习笔记 04 我认为, Git 的学习分为两大块:一是工作区、索引、本地版本库之间的交互;二是本地版本库和远程版本库之间的交互。第一块是基础,第二块是难点。 下面,我们就围绕着第一部分内容来学习,先不考虑远程仓库,只考虑本地仓库。 怎样取得项目的 Git 仓库? 有两种取得 Git 项目仓库的方法。第一种是在本地创建一个新的仓库,第二种是把其他地方的某个

Git 的特点—— Git 学习笔记 02

文章目录 Git 简史Git 的特点直接记录快照,而非差异比较近乎所有操作都是本地执行保证完整性一般只添加数据 参考资料 Git 简史 众所周知,Linux 内核开源项目有着为数众多的参与者。这么多人在世界各地为 Linux 编写代码,那Linux 的代码是如何管理的呢?事实是在 2002 年以前,世界各地的开发者把源代码通过 diff 的方式发给 Linus,然后由 Linus