吸烟者问题
假设系统有三个抽烟者进程和一个供应者进程。每个抽烟者不断地卷烟并抽掉它,但是要卷起并抽掉一支烟,抽烟者需要有三种材料:烟草,纸和胶水。三个抽烟者中,第一个拥有烟草,第二个拥有纸,第三个拥有胶水。供应者进程无限的供应这三种材料,供应者每次将材料放桌子上,拥有剩下两种材料的抽烟者卷一根烟并抽掉它,并给供应者一个信号告诉完成了。供应者就会将放另外两种材料在桌上,这个过程一直重复(让三个抽烟者轮流抽烟)
本质上也属于"生产者-消费者"问题
3.9读者,写者问题
有读者,写者两组并发进程,共享一个文件。当两个或两个以上进程同时访问共享数据时不会产生副作用,但若某个写进程和·其他进程(读进程和写进程)同时访问共享数据的时候可能导致数据不一致的错误。因此要求:
①允许多个读者可以同时对文件执行读操作
②只允许一个写者往文件里写数据
③任意写者在完成写工作的时候不允许其他写者和读者工作。
④写者执行工作前,应让其他读者和写者停止工作
在这个问题上,读者与写者之间需要进行互斥,写者与写者之间需要实现互斥。而读者与读者之间不需要互斥
只有读进程为0的时候,才会进行释放锁,如果在这段时间内,有源源不断地读进程近来,那么锁就无法被释放,写进程就一直拿不到锁进行数据的更新。
优化后:
当有写进程进来的时候,会对这个互斥访问变量进行上锁,但是会等前面的读进程执行完读的操作。而后面的读进程就会被阻塞。
3.10哲学家进餐问题
桌子上坐着五位哲学家,每一个哲学家面前有一根筷子,桌子中间是一碗米饭。哲学家倾注毕生的精力去进餐,哲学家在思考的时候,并不会影响其他人。只有当哲学家饥饿的时候 次啊会尝试拿起左右两根筷子(一根一根地拿起)。如果筷子在其他人手里,则需要等待。饥饿的哲学家只有当拿着两根筷子的时候才可以开始进餐,当进餐完毕后,继续思考
此时有一个问题
如果五位哲学家并发的执行,他们同时拿起左边的筷子,那么此时就会出现彼此都在等待对方释放右边的筷子,而自己却不会主动的释放手里的筷子,那么此时就会出现彼此之间相互等待,进而出现死锁的现象
解决方案
方案一:
可以对同时进餐的哲学家数量做一定的限制,让进餐的哲学家数量小于总共哲学家数量。那么其中必有一个可以拿到完整的筷子数进行进餐,等该哲学家进餐完毕后,就会释放筷子,剩下的哲学家就可以持有该筷子进行进餐。这样就避免了阻塞的情况
方案二:
要求奇数号哲学家先拿左边的筷子,然后再拿右边的筷子。而偶数号的哲学家正好相反。用这种方法,就可以保证如果左右两边哲学家同时想要吃饭,那么其中一位哲学家拿起第一根筷子,另一个哲学家直接就进行了等待,避免了占用一根然后等待另一根的情况
3.11管程
1):为什么引入管程
①:使用信号量机制(P,V操作)容易出现编程困难,易出错。所以Brinch Hansen在1973年引入了“管程”的概念。使得程序员不需要再关注复杂的PV操作。
②:引入管程主要是为了更为方便的实现进程互斥和进程同步
2):管程的定义和特征
①:定义
管程是一种特殊的软件模块,分为四部分组成
a:局部于管程的共享数据结构说明
在代码上类似于类的定义,类里面有定义好的数据
b:对该数据结构进行操作的一组过程
这个过程也叫做“函数”,类似于类的方法
c:对局部于管程的共享数据设置初始值的字句
类似于类的初始化语句
d:管程有一个名字
类的名字
②:基本特征
a:局部于管程的数据只能被局部于管程的过程访问
b:一个进程只有通过调用管程内的过程才能进入管程访问共享数据
c:每次仅允许一个进程在管程内执行某个内部过程(函数方法)
3)扩展1:用管程来解决生产者消费者的问题
步骤:
a:需要在管程中定义共享数据(如生产者消费者问题的缓冲区)
b:需要在·管程中定义用于访问这些共享数据的入口,其实就是一些函数(如生产者消费者问题中,可以定义一个方法用于生产产品,定义一个方法用于消费产品)
c:这又通过特定的入口才能访问这些数据
d:管程中有很多“入口”,但是每次只能开放其中一个“入口”,并且只能让一个进程或者线程进入(这种互斥特性是由编译器实现的)
e:可在管程中设置条件变量及等待/唤醒操作以解决同步问题,可以上一个进程或者线程在条件变量上等待(此时,该进程应该先释放管程的使用权,也就是让出”入口“),可以通过唤醒操作将等待在条件变量上的进程和线程唤醒
这是利用伪代码实现的管程
小结:
4.1死锁
什么是死锁:
在并发的环境下,各进程因竞争资源而造成相互等待对方手里的资源,导致进程都阻塞,都无法向前推进的现象。这就是死锁,发生死锁后,若没有外力涉入,将无法向前推进。
进程死锁,饥饿,死循环的区别
饥饿:表示是进程长期得不到自己想要的资源,某进程无法向前推进的现象。
死循环:某进程执行一直跳不出某个循环的现象,有时是程序逻辑BUG导致的,有时是程序员故意设计的
死锁:多个进程争夺有限的资源,拥有一部分资源的进程等待另外的进程释放资源,而对方也是在等待。
死锁产生的必要条件:
1):只有对必须互斥使用的资源的争夺才能发生死锁,像内存,扬声器这种可以同时让多个进程使用的资源是不会导致死锁的
2):不可剥夺条件,在进程没有使用完资源之前,不能由其他进程夺取,只能主动释放
3):请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源又被其他进程占有,此时请求进程被阻塞,但又对自己已有的资源保持不放
4):循环等待条件:存在一个资源循环等待请求链,链中的每一个进程以获得资源同时被下一个进程所请求
什么时候会产生死锁
1):对不可剥夺的系统资源争夺可能会发生死锁
2):进程推进顺序非法,请求和释放资源的顺序不当,也同样会产生死锁
3):信号量的使用不当也会产生死锁,如生产者-消费者问题中,如果实现互斥的P操作在实现同步的P操作之前就会产生死锁的现象
死锁的处理策略:
1):破坏死锁:破坏死锁产生的四个必要条件中得一个或几个
2):避免死锁。用某种方法防止系统进入不安全状态,从而避免死锁(银行家算法)
3):死锁得检测和解除。允许死锁的发生,但是操作系统会负责检测死锁的发生,然后采取某种方法接触死锁
小结:
4.2死锁得处理策略
不允许死锁发生的情况下,有两种死锁的处理策略。一种是静态策略–预防死锁。一种是动态策略–避免死锁。
1):静态策略–预防死锁
预防死锁的四种方式:1:破坏互斥条件。2:破环不剥夺条件。3:破坏请求和保持条件。4:破坏等待条件
①:破坏互斥条件:
a:只有对必须互斥使用得资源进行争夺才会产生死锁
b:如果将只能互斥使用的资源改造为允许共享使用,则系统不会进入死锁状态,比如:SPOOLing技术
c:该策略的缺点–并不是所有的资源都可以改造成可共享使用资源,为了系统安全性,很多地方还保护这种互斥性
②:破坏不剥夺条件:
不剥夺条件:在进程所获得的资源在未使用完之前,不能由其他进程强行夺走,只能主动释放
方案一:
当某个进程请求新的资源得不到满足的时候,它必须释放保持的所有资源,待以后需要时再重新申请。也就是说,即使某些资源没有使用完毕,也需要主动释放,从而破坏了不可剥夺的条件
方案二:
当某个进程需要的资源被其他进程占有的时候,可以由操作系统协助,将想要的资源强行剥夺。这种一般是需要考虑各进程的优先级
该策略的缺点:
a:实现起来比较复杂
b:释放以获得的资源可能造成前一段工作的失效,因此这种方法一般只适用于易保存和恢复的资源。比如CPU
c:反复的请求和释放资源会增加系统的开销,降低系统的吞吐量
d:如果采用方案一,如果请求不到新的资源就需要重新释放手里的所有资源,以后再重新申请,如果一直都请求不到,就会发生饥饿的情况
③:破坏请求和保持条件:
请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源又被其他进程所占有,此时请求进程被阻塞,但又对自己的保持的资源不放
采用静态分配方法,即一个进程开始运行前就申请完自己所有需要的资源,在未获得所有资源之前,不让其投入运行,一旦运行,这些资源就一直归他所有,该进程就不会再去申请其他资源
缺点:
有些资源可能只需要用很短的时间,因此如果进程在整个运行期间都保持拥有所有资源的话,就会造成资源的浪费,降低资源利用率。另外该策略可能造成某些进程饥饿
④:破坏等待条件
等待链:存在一种进程资源的循环等待链,链中每一个进程以获得的资源同时被下一个进程所请求
可采用顺序资源分配法:
首先给系统中的资源编号,规定每个进程只能按照标号递增的顺序请求资源,同类的资源(即编号相同的资源)一次申请完。
原理分析:
一个进程只有占有小编号的资源的时候,才有资格申请更大编号的资源。按此规则,以持有大编号资源的进程不可能回过头申请小编号的资源,从而不会产生循环等待的现象
该策略的缺点:
a:不方便增加新的设备,因为可能需要重新分配编号
b:进程实际使用的顺序可能与编号递增顺序不一致,会导致资源浪费
c:必须按照次序申请资源,用户编程麻烦
小结:
2):动态策略–避免死锁
什么是安全序列
所谓安全序列,就是如果系按照某种顺序分配资源,则每一个进程都能顺利完成,只要找出一个安全序列,系统就是安全的。
什么是系统的不安全状态,与死锁有何联系
①如果系统分配资源后,系统找不到任何一个安全序列,系统就进入了不安全状态,这就意味着之后可能所有的进程都无法顺利的执行下去。当然,如果有进程提前归还了资源,那系统可能重新进入到安全状态,不过这开始需要考虑最坏的情况的
②如果系统进入了安全状态,就一定不会发生死锁。如果系统进入了不安全状态,就不一定会发生死锁
如何避免系统进入不安全状态–银行家算法:
①而一再资源分配之前预先判断这次分配是否会导致系统进入不安全状态,以此决定是否答应资源分配请求(如果此次的资源分配容易让系统进入不安全状态,就会暂停这次的资源分配请求的响应),这也是“银行家算法”的核心思想
②银行家算法是荷兰学者Dijkstra为银行系统设计的,以确保银行在发放贷款的时候,不会发生不能满足客户需要的情况。后来算法被用在操作系统中,用于避免死锁。
这个银行家算法,在于当有进程有资源请求的时候,系统会考虑满足这次资源分配后,剩余的资源,能否满足其他进程所需的最大需求。如果满足就响应这次的资源需求请求,如果不满足就暂时不响应,如此让系统始终处于安全序列中。
银行家算法步骤:
①检查此次申请是否超过了之前声明的最大的需求数
②检查此时系统剩余的可用资源啊是否还能满足这次请求
③试着分配,更改数据结构
④用安全性算法检查此次分配是否会导致系统进入不安全的状态
数据结构:
①长度为m的一维数组Available表示还有多少可用资源
②n*m矩阵max表示各进程对资源的各自的最大需求数
③n*m矩阵Allocation表示已给进程分配了多少资源
④Max-Allocation=Need表示各进程最多还需要多少资源
⑤用长度为m的一维数组Request表示进程此次申请的各种资源数
假设系统中有n个进程,m个资源
每个进程在运行前先声明对各种资源的最大需求数,则可用一个nm的矩阵(可用二维数组实现)表示所有进程对各种资源的最大需求数。不妨称为最大需要矩阵Max,Max[i,j]=k表示进程Pi最多需要K个资源Rj。同理,系统可以用一个nm的分配矩阵Allocation表示对所有进程的资源分配情况。Max-Allocation=Need矩阵,表示各进程最多还需要多少各类资源
另外还可以用一个长度为m的一维数组Available表示当前系统还有多少可用资源
某进程Pi向系统申请资源,可用一个长度为·m的一维数组Request表示本次申请的各种资源量
允许死锁发生的方式下,操作系统可以进行死锁的检测和解除两种方式
1)死锁的检测
用某种数据结构来保存资源的请求和分配信息,提供一种算法,利用请求和分配信息来检测进程是否进入了死锁状态
这里用得资源分配图来存储资源的分配情况
资源分配图有两种节点
①进程节点:对应一个节点
②资源节点:对应一类资源,一类资源可能有多个
有两种边
进程节点到资源节点的边:表示进程想要申请几个资源(每条边代表一个)
资源节点到进程节点的边:表示已经为进程分配了几个资源(每条边代表一个)
如果系统中剩余的资源满足进程最大的资源需求数,那么这个进程就可以顺利的执行下去,不会发生阻塞。
如果这个进程执行完毕了,就会把资源归还给系统,那么系统就会把其余正在等待·该资源的进程激活,并使其顺利的执行下去
如果按上述流程进行分析,最终能消除所有的边,这样的图就被称为“可完全简化”,此时一定没有死锁的发生(相当于能找到一个安全序列)
如果不能消除所有的边,那么就是发生了死锁
2)死锁的解除:
死锁的解除有三种形式
①:资源剥夺法
挂起(暂时放到外存上)某些死锁进程,并抢夺它的资源。将这些资源分配给其他的死锁进程。但是应防止挂起的进程长时间得不到资源而饥饿
②撤销进程法:
强制撤销部分,全部的死锁进程,并剥夺这些进程的资源。这种方式实现简单,但是付出的代价很大
③进程回退法:
让一个或多个死锁进程回退到足以避免死锁的地步,这就要求系统要记录进程的历史信息,设置还原点
根据什么对进程进行处理:
①:进程优先级比较低的进程
②:已执行多长时间,执行时间越短的进程越可能被处理
③:该进程还有多长时间执行完毕,越长的进程越可能被处理
④:进程已经使用了多少资源,使用资源越多的进程越可能被要求做出牺牲
⑤:进程时交互式的,还是批处理的,批处理的因为更多是数据的处理,并不直接与用户交互。所以优先对此进行开刀
如果系统中不采取预防死锁的措施,也不采取避免死锁的措施,系统就很可能会发生死锁
小结: