目录
信号量机制
前言:
信号量含义
整形信号量
记录型信号量
信号量实现进程互斥
具体过程
信号量机制实现进程同步
实现过程
信号量实现前驱关系
实现过程
生产者消费者问题
具体操作
改变empty、mutexPV操作顺序
多生产者-多消费者问题
分析
执行
吸烟者问题
读写者问题
哲学家进餐问题
管程
为什么要引入管程
管程的定义和基本特征
管程的基本特征
死锁
死锁的必要条件
具体案例
死锁的处理策略
预防死锁
破坏互斥条件
破坏不可剥夺条件
破坏不可剥夺条件方案
缺点
破坏请求和保持条件
破坏循环等待条件
避免死锁
死锁的检测和解除
死锁检测和解除算法
死锁的检测
解除死锁的方法
信号量:信号量实际上就是一个变量(可以是一个整数,也可以是更复杂的记录型变量)可以用一个信号量来表示系统中某种资源的数量,比如:系统中只有一台打印机,就可以设置一个初值为1的信号量
注意:
含义:用一个整形信号量用来表示系统中某种资源的数量
eg:一台计算机有一台打印机
理解:一个线程执行时先执行wait(S) ,此时若另一个线程进来则由于S<=0为真,则会一直循环;直到刚才的进程执行完singal(S)释放信号量
注意:若一个进程(P1)暂时进不了临界区,系统资源不够的话会一直占用处理机,一直循环检查从而导致忙等,不满足让权等待
前言:为了解决整形信号量的忙等问题
含义:用记录型数据结构表示的信号量
理解:一个线程执行时先执行wait(S) ,此时若另一个线程进来,若剩余资源数<0则会使进程从运行态进入阻塞态(阻塞前占一个信号量),并将其挂到信号量S的等待队列L中;直到刚才的进程执行完singal(S)释放信号量才会被唤醒进程。
注意:
进程同步:要让各并发进程按要求有序的推进
理解:要想让代码2在代码4前执行,因为初始信号量为0,首先执行V操作将信号量改为1,再执行下一段代码前执行P操作将信号量改为0(因为S=0,所以不可能先执行P操作;若P先执行则会直接阻塞,一直等到V操作执行后被唤醒)
理解:就相当于多层的同步关系。
前言:系统中有一组生产者进程和一组消费者进程,生产者进程每次生产一个产品放入缓冲区,消费者进程每次从缓冲区取出一个产品并使用(注意:这里的产品理解为某种数据)生产者、消费者共享使用一个初始为空、大小为n的缓冲区
注意:
理解:若此时缓冲区已经放满产品,empty=0,full=n;则生产者生产产品,1锁死,后发现2缓冲区没有了进不了缓冲区就会一直阻塞;来到消费者,因为P已经锁死还未释放,直接进不去(生产者需要V(empty),消费者需要P(mutex)——互相等待对方资源,死锁)
结论:实现互斥的P操作一定要在实现同步的P操作之后,这样才不会发生死锁
例子:桌子上有一个盘儿,每次只能放一个果儿;爸爸放苹果给女儿吃;妈妈放橘子给儿子吃,只有盘儿为空,才可以放水果;仅当盘中有自己需要的水果儿子女儿才消费
互斥关系(mutex=1):对缓冲区的访问要互斥进行
同步关系(一前一后)
注意:这里的P(mutex)、V(mutex)可以省略(因为盘子一共才一个,父亲消耗了,母亲自然没法消耗[省略P(mutex)操作];而父亲和女儿,母亲和儿子又是同步关系[省略V(mutex)操作])
假设有一个系统有三个抽烟者进程和一个供应者进程,每个抽烟者不停的卷烟并将他抽掉,但卷起并抽掉一支烟需要三种材料:烟草、纸、胶水。三个抽烟者中,第一个拥有烟草、第二个拥有纸、第三个拥有胶水。供应者进程无限的提供三种材料,供应者每次将两种材料放在桌子上,拥有剩下那种材料的抽烟者卷一根烟并抽掉它,并给供应者进程一个信号告诉它完成了,供应者就会放另外两个材料到桌子上,这个过程一直重复(供应者目的:吸烟者轮流吸烟)
三种组合
同步关系
前言:有读者和写者两组并发进程,共享一个文件,当两个或两个以上的读进程同时访问共享数据时不会产生副作用,但若某个写进程和其他进程同时访问共享数据时可能导致数据不一致的情况
因此要求
潜在问题:只要读进程还在读,写进程就得一直阻塞等待,可能“饿死” 。因此,这种算法中读进程是优先的
理解:当一个读者进程读文件的时候有一个新的写者进程到达,由于第一个读者进程已经执行了V(w),所以写者进程执行P(w)的操作时不会被阻塞;在执行P(rw)操作时由于第一个读进程已经对P(rw)执行了P操作,所以写者进程会被阻塞在该位置;此时若有第二个读者进程到达的话,由于之前写者进程P(w),没有执行V(w),所以读者进程会被阻塞在P(w)
核心思想:设置一个计数器count用来记录当前正在访问共享文件的读进程数;可以用count值来判断当前进入的进程是否是第一个/最后一个读进程,而做出不同处理
一张圆桌上坐着五位哲学家,每两位哲学家之间的桌子上摆着一根筷子,桌子中间是一碗米饭。哲学家们倾注毕生精力用于思考和进餐,哲学家思考时并不影响其他人。只有当哲学家饥饿时,才试图拿起左右两根筷子(一根根的拿)若筷子已经在他人手上,则需要等待。饥饿的哲学家只有拿起两根筷子才可以开始进餐,当进餐完毕后,放下筷子继续思考
前言:信号量设置,定义互斥信号量数组chopstick[5] ={1,1,1,1,1}用于实现对5个筷子的互斥访问,并对哲学家按0-4编号,哲学家i左边的筷子编号为i,右边的筷子编号为(i+1)%5
关键问题:解决死锁,添加互斥信号量mutex
信号量机制存在的问题:编写程序困难,易出错
管程是一种特殊的软件模块,由这些部分组成
理解:若两个消费者先执行,生产者进程后执行;那么第一个消费者进程在执行的时候会调用管程的remove方法,首先判断此时缓冲区里是否有可用的产品,没有则会等待在empty这个条件变量相关的队列中;同样的,第二个消费者进程开始执行remove的时候也会发现count的值为0,同样等待empty的队列队尾中;之后有一个生产者进程开始执行insert函数,其会将自己的产品放入缓冲区中,并且会检查自己放入的产品是不是缓冲区的第一个产品,若是第一个产品就意味着可能有别的消费者进程正在等待我的产品所以就会唤醒empty队列中的进程,由于第一个进程被唤醒之后就开始执行count--,然后检查自己取走产品前缓冲区是否满了,若缓冲区满了就意味着可能有生产者进程需要被唤醒signal(full),然后返回产品指针取出产品;
注意:
含义:两个线程互相抱着对方需要的资源,互相等待对方的执行结果,形成僵持,并且两个线程均不会释放各自的资源
饥饿:由于长时间得不到想要的资源,某进程无法向前推进的情况
注意:
public class DeadLock {public static void main(String[] args) {Makeup g1 = new Makeup(0, "灰姑娘");Makeup g2 = new Makeup(1, "白雪公主");g1.start();g2.start();}
}
class Lipstick{}
class Mirror{}
class Makeup extends Thread{//需要的资源只有一份static Lipstick lipstick=new Lipstick();static Mirror mirror=new Mirror();int choice;String girlName;Makeup(int choice,String girlName){this.choice=choice;this.girlName=girlName;}@Overridepublic void run() {//化妆try {makeup();} catch (InterruptedException e) {e.printStackTrace();}}private void makeup() throws InterruptedException {if (choice==0){synchronized (lipstick){System.out.println(this.girlName+"获得口红的锁");Thread.sleep(1000);synchronized (mirror){System.out.println(this.girlName+"获得镜子的锁");}}}else {synchronized (mirror){System.out.println(this.girlName+"获得镜子的锁");synchronized (lipstick){System.out.println(this.girlName+"获得口红的锁");}}}}
}
互斥条件:只有对必须互斥使用的资源的争抢才会导致死锁
若把只能互斥使用的资源改造为允许共享使用,则系统不会进入死锁状态。
比如SPOOLing技术,操作系统可以采用SPOOLing技术把独占设备在逻辑上改造成共享设备,如下例子
过程:各个进程对打印机发出的请求会首先被输出进程接收,当他们的请求都被接收后,这些进程就可以顺利的往下执行别的事情了;之后输出进程就会根据各个进程的请求,依次放到打印机上打印输出
缺点:并不是所有的资源都可以改造成共享使用的资源。并且为了系统安全,很多地方还必须保护这种互斥性,因此,很多时候都无法破坏互斥条件
不可剥夺条件:进程所获得的资源在未使用完之前,不能由其他进程强行夺走,只能主动释放
请求和保持条件:一个进程因请求资源而阻塞,对已获得的资源保持不放
可以采用静态分配法,即进程在运行前一次申请他所需要的全部资源,在他资源未满足前,不让他投入运行。一旦投入运行后,这些资源就一直归他所有,该进程就不会请求别的任何资源了
缺点:有些资源可能只需要很短的时间,因此若进程整个期间都一直保持着所有的资源,(有些资源使用的频率并不高)就会造成严重的资源浪费,资源利用率极低。另外,该策略也可能导致某些进程饥饿
循环等待条件:存在一种进程资源的循环等待链,链中的每一个进程已获得的资源同时被下一个进程所请求
可以采用顺序资源分配法。首先给系统中的资源编号,规定进程必须按照编号递增的顺序请求资源,同类资源(编号相同的资源)一次申请完
原理:一个进程只有已经占有小编号的资源的同时才有资格申请大编号的资源。按此规则,已持有大编号的资源进程不可能逆向的回来申请小编号的资源,从而不会产生循环等待
安全序列:若系统按照这种序列分配资源,则每个进程都能完成,只要能找出一个安全序列,系统就是安全状态。当然,安全序列可能有多个
银行家算法的核心思想:在资源分配之前预先判断这次分配是否会导致系统进入不安全状态,以此决定是否答应资源的分配请求(若会进入不安全状态,就暂时不答应这次请求,让该进程先阻塞等待)
例子:系统中有5个进程P0-P4,3种资源R0-R2,初始数量为(10,5,7)则某一时刻的情况可表示
注意:
银行家算法的步骤
安全性算法步骤
为了对系统是否已经发生了死锁进行检测
进程请求完需要的资源后会释放所有资源并且不会申请资源,若根据这种方法最终能消除所有的边,就称这个图是可完全简化的。此时一定没有发生死锁(相当于找到一个安全序列);若最终不能消除所有的边,那么此时就是发生死锁(最终还连着边的进程处于死锁)