目录
2.3_1_进程同步、进程互斥
2.3_1_1什么是进程同步
2.3_1_2什么是进程互斥
2.3_2_进程互斥的软件实现方法
2.3_2_1单标志法
2.3_2_2双标志先检查法
2.3_2_3双标志后检查法
2.3_2_4Peterson算法
2.3_3_进程互斥的硬件实现方法
2.3_3_1 中断屏蔽方法
2.3_3_2TestAndSet指令
2.3_3_3Swap指令
2.3_4_信号量机制
2.3_4_1信号量机制
2.3_4_2信号量机制--整型信号量
2.3_4_3信号量机制--记录型信号量
2.3_5_用信号量实现进程互斥、同步、前驱关系
2.3._5_1信号量机制实现进程互斥
2.3_5_2信号量机制实现进程同步
2.3_5_3信号量机制实现--前驱关系
2.3_6_生产者-消费者问题
2.3_7_多生产者-多消费者问题
2.3_8_吸烟者问题
2.3_9_读者-写者问题
2.3_10_哲学家进餐问题
2.3_11_管程
2.3_11_1为什么要引入管程?
2.3_11_2管程的定义和基本特征
2.3_11_3拓展1:用管程解决生产者消费者问题
2.3_11_4拓展2:Java中类似于管程的机制
首先,给大家来个知识点回顾:进程具有异步性的特征。异步是指,各并发执行的进程以各自独立的、不可预知的速度向前推进。
同步:亦称直接预约关系,它是指为完成某种任务而建立的两个或多个进程,这些进程因为需要在某些位置上协调它们的工作次序而产生的制约关系。进程间的直接制约关系就是源于它们之间的相互合作。
对临界资源的互斥访问,可以在逻辑上分为如下四个部分:
do{
entry section;//进入区---负责检查是否可进入临界区,若可进入,则应设置 正在访问临界资源的标志 (可理解为“上锁”),以阻止其他进程同时进入临界区
critical section;//临界区---访问临界资源的那段代码
exit section;//退出区---负责解除 正在访问临界资源 的标志(可理解为“解锁”)
remainder section;//剩余区---(做其他处理)
}while(true)
注意:
临界区是进程中访问临界资源的代码段
进入区和退出区是负责实现互斥的代码段
临界区也可称为“临界段”
如果一个进程暂时不能进入临界区,那么该进程是否应该一直占着处理机?该进程有没有可能一直进不了临界区?
为了实现对临界资源的互斥访问,同时保证系统整体性能,需要遵循以下原则:
- 空闲让进。临界区空闲时,可以允许一个请求进入临界区的进程立即进入临界区
- 忙则等待。当已有进程进入临界区时,其他试图进入临界区的进程必须等待
- 有限等待。对请求访问的进程,应保证能在有限时间内进入临界区(保证不会饥饿)
- 让权等待。当进程不能进入临界区时,应立即释放处理机,防止进程忙等待。
本节小结:
本节小结:
相比软件实现方法,TSL指令把“上锁”和“检查”操作用硬件的方式变成了一气呵成的原子操作
优点:实现简单,无需像软件实现方法那样严格检查是否会有逻辑漏洞;适用于多处理机环境
缺点:不满足“让权等待”原则,暂时无法进入临界区的进程会占用CPU并循环执行TSL指令,从而导致“忙等”
有的地方叫Exchange指令,或简称XCHG指令
Swap指令是用硬件实现的,执行的过程不允许被中断,只能一气喝成。以下是用C语言描述的逻辑:
逻辑上来看Swap和TSL并无太大区别,都是先记录下此时临界区是否已经被上锁(记录在old变量上),再将上锁标记lock设置为true,最后检查old,如果old为false则说明之前没有别的检查对临界区上锁,则可跳出循环,进入临界区
优点:实现简单,无需像软件实现方法那样严格检查是否会有逻辑漏洞;适用于多处理机环境
缺点:不满足“让权等待”原则,暂时无法像进入临界区的进程会占用CPU并循环执行TSL指令,从而导致“忙等”
本节小结:
用户进程可以通过使用操作系统提供的一对原语来对信号量进行操作,从而很方便的实现了进程互斥、进程同步。
信号量其实就是一个变量(可以是一个整数,也可以是更复杂的记录型变量),可以用一个信号量来表示系统中某种资源的数量,比如:系统中只有一台打印机,就可以设置一个初值为1的信号量。
原语是一种特殊的程序段 ,其执行只能一气喝成,不可被中断。原语是由关中断/开中断指令实现的。软件解决方案的主要问题是由“进入区的各种操作无法一气呵成”,因此如果能把进入区、退出区的操作都用“原语”实现,使这些操作能“一气喝成”就能避免问题。
一对原语:wait(S)原语和signal(S)原语,可以把原语理解为我们自己写的函数,函数名分别为wait和signal,括号里的信号量S其实就是函数调用时传入的一个函数。
wait、signal原语常简称为P、V操作(来自荷兰语proberen和verhogen)。因此,做题的时候常把wait(S)、signal(S)两个操作分别写为P(S)、V(S).
用一个整数型的变量作为信号量,用来表示系统中某种资源的数量。(与普通整数变量的区别:对信号量的操作只有三种,即:初始化、P操作、V操作)
Eg:某计算机系统中有一台打印机:
整型信号量的缺陷是存在“忙等”问题, 因此人们又提出了“记录型信号量”,即用记录型数据结构表示的信号量。
eg:
在考研题目中wait(S)、signal(S)也可以记为P(S)、V(S),这对原语可用于实现系统资源的“申请”和“释放”。
S.value的初值表示系统中某种资源的数目
对信号量S的一次P操作意味着进程请求一个单位的该类资源,因此需要执行S.value--,表示资源数减1,当S.value<0时表示该类资源已分配完毕,因此进程应调用block原语进行自我阻塞(当前运行的进程从运行态->阻塞态),主动放弃处理机,并插入该类资源的等待队列S.L中。可见,该机制遵循了“让权等待”原则,不会出现“忙等”现象。
对信号量S的一次V操作意味着进程释放一个单位的该类资源,因此需要执行S.value++,表示资源数加1,若加一后仍是S.value<=0,表示依然有进程在等待该类资源,因此应调用wakeup原语唤醒队列中的第一个进程(被唤醒进程从阻塞态->就绪态)
本节小结:
注:若考生中出现P(S),V(S)的操作,除非特别说明,否则默认S为记录型信号量。
本节小结:
系统中有一组生产者进程和消费者进程,生产者进程每次生产一个产品放入缓冲区,消费者进程每次从缓冲区中取出一个产品并使用。(注:这里的“产品”理解为某种数据)
生产者、消费者共享一个初始为空、大小为n的缓冲区
只有缓冲区没满的时候,生产者才能把产品放入缓冲区,否则必须等待。
只有缓冲区不空的时候,消费者才能从中取出产品,否则必须等待。
缓冲区是临界资源,各进程必须互斥地访问
思考:能否改变相邻P\V操作的顺序?
本节小结:
问题描述:
1.关系分析。找出题目中描述的各个进程,分析他们之间的同步、互斥关系。
2.整体思路。根据各进程的操作流程确定P\V操作的大致顺序(互斥:在临界区前后分别PV,同步:前V后P)
3.设置信号量。设置需要的信号量,并根据题目条件确定信号量的初值。(互斥信号量初值一般为1,同步信号量的初始值要看对应资源的初始值是多少)
思考:如果不设置互斥信号量,会怎么样?
you can see 即使不设置专门的互斥变量mutex,也不会出现多个进程同时访问盘子的现象。
原因在于:本题中缓冲区大小为1,在任何时刻,apple、orange、plate三个同步信号量中最多只有一个是1.因此在任何时刻,最多只有一个进程的P操作不会被阻塞,并顺利地进入临界区...
思考 :如果盘子(缓冲区)容量为2,不设置互斥信号量,会怎么样?
本节小结:
问题描述:
假设一个系统中有三个抽烟者进程和一个供应者进程。每个抽烟者不停地卷烟并抽掉它,但是要卷起并抽掉一支烟,抽烟者需要有三种材料:烟草、纸和胶水。三个抽烟者中,第一个拥有烟草、第二个拥有纸、第三个拥有胶水。供应者进程无限地提供三种材料,供应者每次将两种材料放桌子上,拥有剩下那种材料的抽烟者卷一根烟并抽掉它,并给供应者进程一个信号告诉完成了,供应者就会放另外两种材料再桌上,这个过程一直重复(让三个抽烟者轮流地抽烟)
本质上这题也属于“生产者--消费者”问题,更详细的说应该是“可生产多种产品的单生产者--多消费者”
本节小结:
问题分析:
1.关系分析。找出题目中描述的各个进程,分析他们之间的同步、互斥关系
2.整体思路。根据各进程的操作流程确定P、V操作的大致顺序
3.设置信号量。设置需要的信号量,并根据题目条件确定信号量初值。(互斥信号量初值一般为1,同步信号量的初始值要看对应资源的初始值是多少)
两类进程:写进程、读进程
互斥关系:写进程--写进程,写进程--读进程。读进程与读进程不存在互斥问题。
写进程和任何进程都互斥,设置一个互斥信号量rw,再写者访问共享文件前后分布执行P\V操作。
读者进程和写者进程也要互斥,因此读者访问共享文件前后也要对rw执行P\V操作
如果所有读者进程在访问共享文件之前都执行P(rw)操作,那么会导致各个进程之间也无法同时访问文件。Key:读者写者问题的核心思想--怎么处理该问题呢?
如何实现:
那如何避免死锁发生呢?
- 可以对哲学家进程施加一些限制条件,比如最多允许四个哲学家同时进餐,这样可以保证至少有一个哲学家是可以拿到左右两只筷子的
- 要求奇数号哲学家先拿左边的筷子,然后再拿右边的筷子,而偶数号哲学家刚好相反。用这种方法可以保证如果相邻的两个奇偶号哲学家都想吃饭,那么只会有其中一个可以拿起第一只筷子,另一个会直接阻塞。这就避免了占有一支后再等待另一支的情况。
- 仅当一个哲学家左右两支筷子都可用时才允许他抓起筷子。
本节小结:
管程是一种特殊的软件模板,有这些部分组成:
- 局部于管程的共享数据结构说明
- 对该数据结构进行操作的一组过程(tips:“过程”其实就是“函数”)
- 对局部于管程的共享数据设置初始值的语句
- 管程有一个名字
管程的基本特征:
- 局部于管程的数据只能被局部于管程的过程所访问
- 一个进程只有通过调用管程内的过程才能进入管程访问共享数据
- 每次仅允许一个进程在管程内执行某个内部过程
引入管程的目的无非就是要更方便地实现进程互斥和同步。
1.需要在管程中定义共享数据(如生产者消费者问题的缓冲区)
2.需要在管程中定义用于访问这些共享数据的“入口”--其实就是一些函数(如生产者消费者问题中,可以定义一个函数用户将产品放入缓冲区,在定义一个函数用于从缓冲区取出产品)
3,只有通过这些特定的“入口”才能访问共享数据
4.管程中有很多“入口”,但是每次只能开放其中一个“入口”,并且只能让一个进程或线程进入(如生产者消费者问题中,各进程需要互斥地访问共享缓冲区。管程的这种特性即可保证一个时间段内最多只会有一个进程在访问缓冲区。注意:这种互斥特性是由编译器负责实现的,程序员不用关心)
5.可在管程中设置条件变量及等待/唤醒操作以解决同步问题。可以让一个进程或线程在条件变量上等待(此时,该进程应先释放管程的使用权,也就是让出“入口”);可以通过唤醒操作将等待在条件变量上的进程或线程唤醒。
本节小结: