操作系统之进程—临界区管理 (二)

1.临界区管理

 临界区:并发进程中与共享变量有关的程序段

  •  临界资源:共享变量代表的资源

2.临界区解决互斥问题

如果能保证进程在临界区执行时,不让另一个进程进入临界区,即各进程对共享变量的访问是互斥的,就不会造成与时间有关的错误

3.临界区的调度原则

  • 一次至多允许一个进程进入临界区内

  • 如果已有进程在临界区中,试图进入此临界区的其他进程应等待;
  • 进入临界区的进程应在有限时间内退出,以便让等待队列中的一个进程进入

临界区调度原则可总结成四句话:互斥使用、有空让进;忙则等待、有限等待;择一而入、让权等待;算法可行  算法可行是指不能因为所选的调度策略造成进程饥饿甚至死锁

4.实现临界区管理的软件方法

(1)Dekker算法

Dekker算法用一个指示器turn来指示应该哪一个进程进入临界区。

  • 若turn=0则进程P1可以进入临界区
  • 若turn=1则进程P2可以进入临界区

算法实现

前提条件:共享变量有turn和flag[],turn==0&&flag[1]==ture则轮到po运行临界区代码;turn==1&&flag[1]==ture则轮到p1运行临界区代码

  1. 首先要运行的线程将自己对应的flag置为true,ture代表请求访问(小学生举手提问)
  2. 接着判断其他县城是否也将自己的flag也置为ture(判断其他小学生是不是也举手了)
  3. 如果其他线程的flag为false开始判断trun的值,如果turn的值为1也就是说轮到了其他线程,则将当前线程的flag置为false,并开始循环等待,等待条件就是监测turn的值,当turn的值改变的时候,将当前县城的flag置为true,开始临界区访问,访问结束将turn值改变并将flag置为false(如果有其他小学生举手了,就要判断是不是现在轮到其他小学生,如果是轮到其他小学生则让当前小学生把手放下并且等其他小学生提问完,当提问完后再把举手,之后提问问题,问题提问完后把话语权给其他小学生并且当前小学生把手放下)
1)P0的逻辑

while(true){
    flag[0] = true;// 首先P0举手示意我要访问
    while(flag[1]) {// 判断P1是否也举手了
        if(turn==1){// 如果P1也举手了,那么就看看到底轮到谁
            flag[0]=false;// 如果确实轮到P1,那么P0先把手放下(让P1先)
            while(turn==1);// 只要还是P1的时间,P0就不举手,一直等
            flag[0]=true;// 等到P1用完了(轮到P0了),P0再举手
        }
        flag[1] = false; // 只要可以跳出循环,说明P1用完了,应该跳出最外圈的while
    }
    visit();// 访问临界区
    turn = 1;// P0访问完了,把轮次交给P1,让P1可以访问
    flag[0]=false;// P0放下手
}
2)P1的逻辑
while(true){
    flag[1] = true;// 先P1举手示意我要访问
    while(flag[0]) {// 如果P0是否也举手了
        if(turn==0){// 如果P0也举手了,那么久看看到底轮到谁
            flag[1]=false;// 如果确实轮到P0,那么P1先把手放下(让P0先)
            while(turn==0);// 只要还是P0的时间,P1就不举手,一直等
            flag[0]=true;// 等到P0用完了(轮到P1了),P1再举手
        }
        flag[0] = false; // 只要可以跳出循环,说明P0用完了,应该跳出最外圈的while
    }
visit();// 访问临界区
turn = 0;// P1访问完了,把轮次交给P0,让P0可以访问
flag[1]=false;// P1放下手

(2)Peterson算法

Peterson算法是一个实现互斥锁的并发程序设计算法,可以控制两个线程访问一个共享的单用户资源而不发生访问冲突。Gary L. Peterson于1981年提出此算法

此算法需要将步骤首先需要队turn赋值

void procedure0(){
    while(true){
        flag[0]=true;
        while(flag[1]&&turn==1);/*若flag[1]为false,P0就进入临界区;若flag[1]为tureP0循
                                    环等待,只要P1退出临界区,P0即可进入*/
        visit();/*访问临界区*/
        flag[0]=false;/*访问临界区完成,procedure0释放出临界区*/
        turn=1;
        /*remainder section*/
    }
}
void procedure1(){
    while(true){
        flag[1]=true;
        while(flag[0]&&turn==0);/*若flag[1]为false,P1就进入临界区;若flag[0]为tureP0循
                                    环等待,只要P0退出临界区,P1即可进入*/
        visit();/*访问临界区*/
        flag[1]=false;/*访问临界区完成,procedure1释放出临界区*/
        turn=0;
        /*remainder section*/
    }
}

(3)测试只用一个标志变量 

发现也可以实现临界区管理

    @Override
    public void run() {
        while(true){
            while(turn==ids){
                ss=(ss+1)%12000;
                if(ss==0) System.out.println("饿死了");
            }
            pp();
            turn=ids;
        }
    }

    public void pp(){
        number+=1;
        number+=1;
        System.out.println("this number id :"+id+"is number:"+number);
        number-=1;
        number-=1;
    }

5.实现临界区管理的硬件设施

  • 由于进程切换需要依赖中断来实现,如果屏蔽中断,则不会引起进程切换
  • 因此为了实现对临界资源的互斥使用,可以在进程进入临界区之前关闭中断,等进程退出临界区以后在打开中断
  • 中断被屏蔽后,处理器不响应中断,则不会被切换
  • 于是一旦屏蔽中断,进程就可以进入临界区修改访问共享变量,而不用担心其他进程介入

(1)关中断 

  • 关中断是实现互斥的最简单方法之一
  • 进程在测试标志之前,首先关中断,直到测试完并设置标志之后才开中断
  • 进程在临界区执行期间,计算机系统不响应中断
  • 因此,不会转向调度,也就不会引起进程或线程切换,正在执行标志测试和设置的进程或线程不会被打断,从而保证了互斥

关中断方法的缺点

  • 关中断时间过长会影响系统效率,限制处理器交叉执行程序的能力
  • 关中断方法也不适用于多CPU系统,因为,在一个处理器上关中断,并不能防止进程在其他处理器上执行相同临界区代码

硬件设施缺点

  • 忙等现象仍然存在,但由于使用机器指令测试,则忙等时间可以忍受 (忙等的含义是当一个进程位于其临界区内时,任何试图进入其临界区的进程都必须在其进入代码中连续地循环)
  • 导致饥饿:临界区空闲时,执行循环检测的若干进程能进入临界区的几率是相等的,有的进程可能运气很不好,很难有机会进入临界区,因而饥饿
  • 导致死锁:进程P1和P2,P1的优先级低于P2,P1通过测试进入临界区,这时P2到, P2抢占了P1的处理器开始执行, P2也要求进入临界区,但临界区被P1占,而P1的优先级没有P2高不可能抢占P2的处理器。这样,这两个进程互相等待, P1等P2释放处理器, P2一直忙等P1释放临界区,如果没有外力,这两个进程处于死锁
  • 不会成为一种通用的方法

解决方案

  • 软件方法和硬件方法都存在忙等问题,浪费了处理器的时间
  • 信号量方法可以实现进程同步与互斥,而不需要忙等

6.总结

通过临界区的学习,我们可以知道主要是通过标志变量决定临界区进入的决定权,当一个线程进入临界区的时候,其他线程通过循环等待也就是忙等来等待进入临界区。由于忙等的问题会浪费处理器时间,所以接下来我们学习下一章节信号量来解决忙等这一缺陷的问题。

猜你喜欢

转载自blog.csdn.net/m0_37834471/article/details/83717016