进程互斥(间接制约关系,因为共享存在互斥)
临界资源
临界资源:一段时间内,仅允许一个进程访问。例如,物理设备(摄像头,打印机)许多变量、数据、内存缓冲区都属于临界资源。
属于临界资源,临界资源要求互斥共享
非共享数据一定不是。私有数据仅供一个进程使用,不存在临界区问题。可重入的程序代码一次可供多个进程使用。磁盘存储介质属于共享设备。
公用队列可供多个进程使用,但一次只可一个进程使用,属于临界资源。
对临界资源的访问,就属于进程互斥。逻辑上分为四个部分:
①进入区:上锁
②临界区(临界段):要访问临界资源的那段代码段
③退出区:解锁
④剩余区:做其他处理
进入区和退出区负责进程互斥的代码段✓
临界区是指进程中用于实现进程互斥的那段代码×
进程互斥的原则
空闲让进
忙则等待
有限等待:保证不会饥饿
让权等待:不忙等,进不了临界区就释放处理机
进程互斥的实现(互斥锁)
1.软件实现
(1)单标志位:使用turn谦让。违背空闲让进
①执行while循环,若不是1则一直循环。
①直到P1用完操作系统给他的时间片后,操作系统发生调度,切换另一进程P0上处理机,有可能分配给他但是不用,违背空闲让进
(2)双标志位:使用flag[]表达意愿
①双标志位先检查法:先检查意愿后上锁,违背忙则等待。可能同一时刻多进程进入临界区
双标志检查法使用flag数组 flag[1]=true自己想用,表达意愿,while(flag[0])检查。如果为true则一直循环,一直检查,直到检查出来或者时间片用完。违背忙则等待(临界区被访问时,其他进程应该等待)的原则。有可能以1526的顺序执行,原因检查与上锁不是一气呵成的。
②双标志位后检查法,先表达意愿再检查别人意愿。缺点:违背空闲让进,会饥饿
但是按照1526顺序,老查小查都检测到对方想用临界区,就没有进程使用。违背了空闲让进,有限等待原则,会饥饿。
(3)Petersom算法flag[]=false,turn=0。表达意愿flag[i]=ture,谦让turn=1。缺点无让权等待
谁最后设置turn值,就是谁最后做出的谦让动作,则失去优先动作权。
以162738进行时,16表示两个人都表达意愿,27表示都谦让。但是7是最后,运行到3时,并不符合自己谦让这个动作,就是turn不是1,所以跳出while循环。
2.硬件实现
(1)中断屏蔽方法不适用多处理器。单个处理器中可防止别的进程访问临界区,但不同处理器有可能会访问同一临界区。
(2)TestAndSet指令/TS/TSL。与双标志位一样,只不过把检查和上锁用硬件实现,使用一气呵成的原子操作。适用于多处理机,无让权等待。
会有忙等现象,在检查前就已经上锁了(*lock=ture),如果已经上锁了,但是检查时候原始状态没锁,就一直在循环里
(3)Swap指令/XCHG/Exchange,适用于多处理机。无法让权等待
进程互斥的实现(利用记录型信号量解决让权等待)
用户进程可以使用操作系统提供的一对原语对操信号量进行处理,从而达到进程互斥与进程同步。P(S) wait(S) V(S) signal(S)
原语通过关/开中断实现信号量机制的引入使用阻塞唤醒操作解决让权等待。
信号量可以是整型信号量或更复杂的记录型信号量,表示资源的数量。
整型信号量的缺点是无法实现让权等待,会出现忙等,其实跟双标志位先检查算法一样(缺点是违背忙则等待,可能多个进程访问临界区),只不过是用了原语实现一气呵成
记录型信号量的引入,可以通过唤醒阻塞原语,实现让权等待
wait与signal都是对value先进行操作,再检查。是否需要自我阻塞(从运行态进入阻塞即等待队列)block和被唤醒wakeup(从阻塞进入就绪状态)。阻塞是等待队列
使用互斥信号量Semaphore mutex=1
临界区前P(mutex)
临界区后V(mutex)
记录型信号量实现进程同步(直接制约关系,用来协调)
进程并发具有异步性,这也不可预知的速度往前进,那么如果要保证进程按一定的顺序往前进,则是进程同步需要考虑的问题。
Semaphore S= 按实际情况定,一般表示系统中资源数量
前操作V P后操作:前操作后释放V,占用P进行后操作
实际问题:互斥要放在同步之后,否则死锁
(1)前驱:进程同步(需要协调)
(2)生产者消费者问题:
①单生产者-单消费者:一种互斥关系,两种同步问题(盘空才生产,盘满才消费)
②多生产者-多消费者:一种互斥关系(容易弄成多种互斥关系),三种同步关系(父亲放完女儿才吃,母亲放完儿子才吃,盘空才放)
③单生产者-多消费者:一种互斥关系,四种同步(用完才放,放组合一甲才用,放组合二乙才用,放组合三丙才用)
(3)读者写者问题
互斥上满足读读不互斥,读写互斥,写写互斥
①引用互斥变量,实现写写互斥,写读互斥,读读互斥
②要实现读者之间不互斥,使用一个count,第一个读者上锁,最后一个读者解锁
③要实现读者之间不阻塞,有可能检查完count=0之后结果进程二又去检查且上锁,但进程1会阻塞在上锁那个地方
使用mutex保证对count访问是互斥的
④若源源不断的读者进程,会导致写者饿死,引用P(W)
(4)哲学家进餐问题:进程需要访问多个临界区,为防止死锁有三种方法。
①第一种方法,最多允许四个哲学家进餐,可设初始值I=4。
②第二个方法要求奇数偶数拿的筷子不一样,可以在p chopstick[i]之前判断它的奇偶。
③第三种方法需要每个哲学家拿筷子是互斥,这样其他的哲学家无论是没拿到筷子或拿一半筷子而阻塞,都会保证有一个哲学家拿到两只筷子,等两只筷子都释放之后,会顺序的把阻塞队列唤醒。
管程(互斥访问是由编译器实现,在管程中设置条件变量和唤醒阻塞实现同步)
解决信号量编程麻烦,易出错问题,三部分组成:
①局部于管程的共享数据结构说明。共享数据结构
②对局部于管程的共享数据设置初始值。的语句。对数据结构初始化的语句
③一组对数据结构进行操作的过程也叫函数
特点:进程和线程只能通过函数(入口、过程)访问共享数据,而且每次只能有一个进程/线程访问
过程:empty和full是条件变量,没有值,因为缓冲区空而阻塞的进程放入empty队列,因为缓冲区满而阻塞放入full队列。
例如生产者调用insert(item),当发现count=n时,把该生产者挂到full队列,如果不是N,则count++,插入商品。此时判断如果count=1,也就是第一个放进去的,就执行唤醒阻塞的消费者队列V(empty)。
消费者判断此时队列是否为空n=0,如果空,挂到empty队列中wait(empty),否则就count--,然后再判断此时count是否<=n- 1,意思是如果还有空位,则唤醒full队列中生产者signal(full),生产者继续生产。
如果多个消费者被挂起,执行先到再队首,然后依次排到队尾。
死锁,饥饿与死循环区别
区别
死锁
预防死锁
(1)破坏互斥条件例如采用SOOPLing将打印机共享。遇到必须互斥的可行性不高。
(2)破坏不剥夺条件。会饥饿
①立即释放所持资源
②利用操作系统协助强行剥夺资源
(3)破坏请求和保持条件:运行前分配好所需资源,然后一直保持。会饥饿
(4)破坏循环等待条件:把资源编号,必须按照编号从小到大顺序访问资源。编程麻烦
避免死锁(安全序列银行家问题)
辨析:安全状态一定不死锁,死锁一定是不安全状态,不安全状态也不一定会死锁。找到一种安全序列不一定是安全状态。
①判断request是否大于need
②判断request是否大于available
③尝试分配
Available=available-request
Allocation=allocation+request
need=need-request
④有安全性算法检查此次操作会不会进入不安全状态?
手算是直接比对available与所有need,能匹配上的都回收,再进行更新匹配。若最后都能回收,则存在安全序列。
死锁检测
利用资源分配图(注:系统资源图不可完全简化表明是死锁,但不是图中所有的进程都处于死锁状态)
①找出既不阻塞(请求边有资源可分配)又不是孤点的进程。
②去掉他的所有边(即回收),重复①继续找点,直到所有边都消除了。
若全部消除则是可完全简化的,处于安全状态
如何解锁:
资源剥夺、撤销进程(终止进程)、进程回退