🌈个人主页:小新_-
🎈个人座右铭:“成功者不是从不失败的人,而是从不放弃的人!”🎈
🎁欢迎各位→点赞👍 + 收藏⭐️ + 留言📝
🏆所属专栏:操作系统 欢迎订阅,持续更新中~~~
✨让小新带着你快乐的学习吧~✨
前言
PV大题考查使用伪代码控制进程之间的同步互斥关系,它需要我们一定的代码分析能力,算法设计能力,有时候会给你一段伪代码让你补全使用信号量控制的操作,请一定不要相信只要背一个什么模板,记住什么套路就能拿下这个大题,这是不切实际的!!
在真题的考查中,题目是较为灵活的,需要我们临场分析,如果只要背一背就能拿满分没有区分度,那出题老头也没必要年年都考 PV了对吧?因此在正式进入大题讲解之前,必须对一些 前置知识理解透彻,比如什么是同步与互斥,信号量的分类,以及考纲上的三个模型(生产者消费者、读者写者、哲学家进餐)尤其需要彻底搞懂!!
这里我会大家学习考查的最多的模型一消费者生产者模型,从真题来看,至少有三分之三以上的 PV 问题都是基于此模型进行拓展(比如加一些资源,多一些同步关系等)。并且大家在分析其他模型 的时候,也要达到一个标准:
1.能够理解清楚每一行 PV的意义是什么,为什么这里需要同步,这里要互斥。
2.能够熟练手写考纲上三个模型的代码,不允许出错。
3.真题考查的问题,一定要想一下它和我们已经学过的模型的区别与联系,只有多思考才能在下一次考查到相同类型问题的时候能够熟练写出来。
一、前置知识
- 临界资源:一次只允许一个进程去访问,比如打印机。
- 临界区:进程对于临界资源访问的程序称为临界区,一般需要加上"互斥信号量”(下面会讲)
- 互斥关系:由于进程的并发性同时去访问同一个临界资源会出现问题,需要互斥地去访问。比如两个进程同时访问一个打印机,数据会错乱。
- 同步关系:进程之间的操作事件存在时序关系,比如先后关系,我们在真题会遇到这种说法"操作C必须在操作 A、B 之后完成",也就是在告诉你这里有一个同步关系,需要你用信号量去定义。
- 信号量:对于信号量,我们不要去研究其复杂的概念,它的核心就是:阻塞一唤醒,我们在程序设计的时候也只要思考:我这个信号量是为了阻塞什么操作,什么时候会被唤醒,因此PV总是成对出现,这是本质。
- a.互斥信号量:初始值为1,取值只能为0或1,当有进程进入临界区时就要上锁,也就是P一下互斥信号量,离开的时候就V一下互斥信号量,千万不要忘记释放。
- b.同步信号量:这个名字是我自己编的,仅仅是更利于我们做题。它的初始值需要根据题目去分析,本质是用来实现进程之间的同步操作,也就是阻塞一唤醒功能,当它的值≤0时,说明资源已经被分配完,此时进程就不能获取该资源,需要等待其他进程去唤醒。比如我们的盒子里能放三块巧克力,杰尼龟投放巧克力,小火龙吃巧克力;当盒子里没有巧克力但是小火龙还想要吃,就会 被阻塞住,直到杰尼龟投放了一块巧克力去唤醒小火龙告诉他:"嘿小火龙,巧克力来了,可以享用了!"
二、解题技巧
在大题中,我们只会用到者两种信号量,不要害怕,非此即彼。互斥信号量初始值恒为1,而同步信号量,需要我们去设计。
- 有一些题会初始化为资源的个数,比如缓冲区有10个空位,empty信号量就定为10。
- 有一些题会初始化为 0,比如我们要实现一些"强同步"关系的时候,像等待叫号与叫号服务。
- 有一些题我们画出一个前驱图即可快速解决(类似于数据结构的拓扑排序)。
- 程序中 PV 数量必须相同(PV 成对),请仔细检查。
- 对于单个进程,往往进需要定义一个同步互斥量,而对于两个进程需要一对,三个进行就用三个,如生产者消费者模型,等会用例题和真题带大家感受。(不想思考的时候直接用,无敌!)
- 同步信号量一定要写在互斥信号量外侧(否则会死锁)
- 尽可能的少拿资源,比如一个题他的逻辑是水缸里有水,才能用水桶打水,那么我们在写伪代码的时候,就可以去提前判断一下水缸里是否有水(P一下),如果没有那我们就不去拿水桶了,也就是说当出现多同步关系的时候,我们要去思考一下顺序。
- 一个进程中,如果想实现“同步”关系,往往采用互斥信号量实现,这也是我为什么在前面就说在操作系统中同步和互斥有些情况是不做区分的,互斥就是特殊的同步。因此一个题中互斥信号量不一定只需要一个,不要教条。
基本设计模板:
A(){
P(mutex);
操作;
V(mutex);
}
//先A后B同步关系
A(){
操作;
V(同步信号量);
}
B(){
P(同步信号量);
操纵;
}
三、生产者—消费者模型
分析方法:所有的PV大题,都按照以下步骤进行分析,这里用生产者消费者模型来举例。
- 找到题目中的进程个数,以及资源的个数
- 分析各个进程之间的同步关系,找到需要互斥访问的临界区
- 定义出互斥信号量和同步信号量
- 根据进程同步关系,写出伪代码
- 检查 PV 是否成对,是否有死锁问题(对 mutex 操作放在中间)
- 模拟一下自己的伪代码,看看是否有遗漏的信号量(如果自信可不做)
我们来看这个模型,如图所示有一个缓冲区,假设可以容纳n个产品,生产者负责投放产品,而消费者负载消费产品,图中的产品就是食物。请用PV操作,实现同步互斥关系,要求给出伪代码。
分析:
1.两类进程:生产者、消费者
2.两类资源:大小为n的缓冲区,产品(注意两类 资源不一定只需要两个信号量,需要根据关系而定)
3.关系分析:
a.互斥访问缓冲区
b.缓冲区有空位置才能生产,有产品才能消费
4.信号量定义,并写出伪代码(直接看我写的)
semaphore mutex = 1 ; / 缓冲区互斥访问信号量semaphore empty = n ; / 缓冲区空余位置semaphore full = 0 ; / 缓冲区产品个数/ empty + full = n; 始终成立void producer (){while ( 1 ){//不断重复生产生产一个产品 ;P ( empty );//判断是否有空位,没有就阻塞P ( mutex );//对缓冲区上锁将产品放入缓冲区 ;V ( mutex );//解锁(不能忘记)V ( full );//产品+1,去唤醒被阻塞的消费者}}void consumer (){while ( 1 ){P ( full );//判断是否有产品P ( mutex );从缓冲区取一个产品 ;V ( mutex );V ( empty );//空闲+1,唤醒消费者消耗产品 ;}}
Q:想一想能不能把对于缓冲区上锁的操作与P(同步互斥量)的操作互换?
不行,会死锁。举个例子,小火龙没有判断盒子里有没有巧克力,就直接获取盒子,再去判断有没有巧克力,但是此时盒子里没有巧克力,因此小火龙被阻塞住了,也没有释放盒子;这时杰尼龟来了,准备投放巧克力,想获取盒子的时候发现盒子还没有被释放,因此也被阻塞住了,也就不能生产巧克力去唤醒小火龙。此时进程就死锁了。
Q:有同学可能会问了,判断缓冲区中有没有资源,和减一操作可以分开吗?是不可以的,这是原子性操作,不可分。请看大黑书《深入理解计算机系统》原话:
至此,我们已经搞懂了 PV大题的本质,学习了最常见的模型,剩下的就是通过真题的训练去摸索出套路,熟能生巧,同时也要多思考模型的设计,以不变应万变。
四、实战
2、
最后,感谢大家的观看!