操作系统学习8同步互斥问题

回顾一下同步互斥的概念:

现代操作系统基本都是多任务操作系统,即同时有大量可调度实体在运行。在多任务操作系统中,同时运行的多个任务可能:

  • 都需要访问/使用同一种资源
  • 多个任务之间有依赖关系,某个任务的运行依赖于另一个任务

这两种情形是多任务编程中遇到的最基本的问题,也是多任务编程中的核心问题,同步和互斥就是用于解决这两个问题的。


互斥:是指散步在不同任务之间的若干程序片断,当某个任务运行其中一个程序片段时,其它任务就不能运行它们之中的任一程序片段,只能等到该任务运行完这个程序片段后才可以运行。最基本的场景就是:一个公共资源同一时刻只能被一个进程或线程使用,多个进程或线程不能同时使用公共资源。


同步:是指散步在不同任务之间的若干程序片断,它们的运行必须严格按照规定的某种先后次序来运行,这种先后次序依赖于要完成的特定的任务。最基本的场景就是:两个或两个以上的进程或线程在运行过程中协同步调,按预定的先后次序运行。比如 A 任务的运行依赖于 B 任务产生的数据。


显然,同步是一种更为复杂的互斥,而互斥是一种特殊的同步。也就是说互斥是两个任务之间不可以同时运行,他们会相互排斥,必须等待一个线程运行完毕,另一个才能运行,而同步也是不能同时运行,但他是必须要安照某种次序来运行相应的线程(也是一种互斥)!因此互斥具有唯一性和排它性,但互斥并不限制任务的运行顺序,即任务是无序的,而同步的任务之间则有顺序关系。


一般涉及到同步和互斥的问题,都是线程级别之间的问题。因为一般情况下进程都是认为有各自的数据空间。但是其实对于现代操作系统来说,很多时候进程和线程直接的概念并没有那么明晰。


1、同步的问题,主要是进程或线程有一些共享资源的情况下要考虑的。为了解决不确定性,要引入同步互斥。

系统缺陷:结果依赖于并发执行或者事件的顺序和时间,这就是不确定性和不可重复性。这种现象称为竞态。这是我们要极力避免的。

最好的方法是让指令不会被打断。所以必须使用原子操作。 


2、临界区、互斥、死锁、饥饿的概念:


3、临界区的特点(属性):


4、如何实现对临界区代码的保护(也就是实现互斥的机制)?

(1)禁用硬件中断。

回顾一下并发概念:并发,在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上运行



另外,屏蔽中断在多CPU的机制下是无法解决的,因为如果某个资源是两个CPU并行执行,那是无法成功的,因为一个CPU屏蔽中断的能力只是对于该CPU而言,而不是对于其他CPU。因此在多CPU的时候,中断是无法解决互斥问题的。

(2)基于软件的解决方法

即基于一些软件和编程上的设计使之满足临界区的属性。

(3)更高级的抽象

5、信号量(Semaphore)

背景:


确保同步的各个机制:


但是,这样是不够的。例如,我们希望临界区内可以有多个线程在执行等。这样的话,有哪些更高级的同步手段?可以通过信号量来实现这个机制。信号量在早起的操作系统中是主要的同步原语,例如原Unix中,现在很少用了。

信号量是什么呢?信号量是一个整形,此处称为sem。sem只有两个原子操作:P()和V()


其实很类似铁路上的信号灯。

6、信号量的使用


一般信号量被初始化为一个正数。如果有进程想进入临界区,则执行P(),如 果有进程要退出临界区,则执行V()。

信号量完全可以完成锁的功能。锁与信号量最大的区别是锁在临界区中只有一个进程,而信号量可以有多个。


(1)使用二进制信号量,也就是只有0和1两个值

1)使用二进制信号量实现互斥,其实就跟锁一样。把信号量初始值设为1,在进入临界区之前进行P操作,也就是获取锁。结束后进行V操作,也就是放弃锁


2)实现调度约束,也就是同步操作

信号量初值设成0。在这个例子中,线程A必须要等到线程B执行到某个程度后再执行。


(2)一般/计数信号量

解决这个问题,一般是一个线程在等待另一个线程处理事情。下面是个例子:



7、信号量的实现

信号量使用一个整形和一个队列来实现。等待队列是一系列等待的线程。


信号量的优缺点:


虽然信号量大量使用在实际的操作系统中,但是还是略显复杂,容易发生各种死锁等出错现象。有没有更好更简单更方便的方法来让开发者实现同步互斥呢?这里就需要使用管程。

8、管程(monitor)

什么是管程:包含了一系列共享变量以及针对这些变量操作的函数的组合和模块。

管程比信号量抽象程度更高,但是对于开发者来说也越容易使用。

引入管程机制的目的:1、把分散在各进程中的临界区集中起来进行管理;2、防止进程有意或无意的违法同步操作;3、便于用高级语言来书写程序,也便于程序正确性验证


9、解决同步互斥问题总结:



下面讲死锁


10、死锁的四个条件(如果四个条件同时成立,可能会有死锁,但是不凑齐这四个条件一定没有死锁):



对于图2来说,没有满足持有并等待条件,因为资源R1和R2有两个实例,而P4和P2可以持有R2和R1并且执行,执行完后,资源就释放了,就可以继续进行下去了。所以虽然这里满足了循环等待条件,但是并不会有死锁。

11、死锁的处理

死锁预防->死锁避免->死锁检测->死锁恢复。他们的条件是逐步减弱的。一般来说,死锁检测和死锁恢复是结合在一起使用的,毕竟检测到了有死锁,当然要恢复。

对于一个操作系统来说,一般这样设计:


为什么大部分都忽略这个问题呢?判断是否出现死锁以及恢复的开销很大;如果设置一系列的约束不能进入死锁状态,会令操作系统的功能受到很大限制,很多任务无法完成。这被称为鸵鸟办法,忽略死锁这个问题,如果遇到了这个问题,再想办法解决。

(1)死锁预防

也就是让死锁绝对不会出现。思路就是,打破死锁四个条件中的某个条件就可以了。

1)互斥:把资源变成可以共同占用的

2)占用并等待:保证当一个进程请求资源时,它不持有其他任何资源。会造成资源利用率低,可能会发生饥饿

3)无抢占:如果进程占有某些资源,并请求其他不能被立即分配的资源的时候,释放当前正在占有的资源

4)循环等待:对所有资源类型进行排序,并要求每个进程按照资源的顺序进行申请

(2)死锁避免

相比死锁预防更小的限制程度。它不是通过一些机制来绝对预防死锁,而是在进程申请资源的时候,判断是否会产生死锁。如果有可能出现死锁,不能分配资源给这个进程。所以需要系统具有一些额外的先验信息



这里的安全状态,不止是不会陷入死锁的状态,而是比不会陷入死锁更大的一个状态。


有一个著名的死锁避免算法:银行家算法。归根到底,银行家算法就是判断一个序列是否安全的算法。


银行家算法的具体实现:


存在关系Need[i,j]=Max[i,j]-Allocation[i,j]

对于具体的算法实现来说,首先是安全状态判断算法


然后是整体的银行家算法


(3)死锁检测

允许系统进入死锁状态,隔一段时间调用死锁检测算法,如果系统进入死锁了,就启动恢复机制

1)怎么检测系统是否进入死锁?

常用方法:建立资源分配图,把资源的R去掉,如果产生环了就是进入死锁状态了


检测算法的使用需要考虑什么问题:


一个概念,回滚:回滚(Rollback)指的是程序或数据处理错误,将程序或数据恢复到上一次正确状态的行为。回滚包括程序回滚和数据回滚等类型。

(4)死锁恢复

关键是恢复的进程,需要定一种规则




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章