临界区算法:Peterson算法与Dekker算法

当时老师在课上讲的时候就没搞懂

现在要写作业了果然还是得靠自己学明白啊=_=


下面的概念都来自于《操作系统概念》这本书


首先说一下临界区问题(Critical Section Problem)

临界区的问题的解答必须满足以下三个条件:

1)     互斥:如果进程Pi在其临界区内执行,那么其他进程都不能在其临界区内执行。

2)     有空让进:如果没有进程在其临界区内执行且有进程需进入临界区,那么只有那些不在剩余区内执行的进程可参加选择,以确定谁能下一个进入临界区,且这种选择不能无限推迟。

3)     有限等待:从一个进程做出进入其临界区的请求,直到该请求允许为止,其他进程被允许进入其临界区的次数有上限。

临界区算法:Peterson算法与Dekker算法_第1张图片

临界区算法:Peterson算法与Dekker算法_第2张图片

临界区算法:Peterson算法与Dekker算法_第3张图片

临界区算法:Peterson算法与Dekker算法_第4张图片

临界区算法:Peterson算法与Dekker算法_第5张图片


接下来说一下

Peterson算法

临界区算法:Peterson算法与Dekker算法_第6张图片


Peterson算法是一个实现互斥锁的并发程序设计算法,核心就是三个标志位是怎样控制两个方法对临界区的访问,这个算法设计的想当精妙,我刚开始看的时候就被绕了一下。

算法使用两个控制变量flag与turn.

其中flag

的值为真,表示ID号为n的进程希望进入该临界区.

标量turn保存有权访问共享资源的进程的ID号.

flag[] is boolean array; and turn is an integer
flag[0]   = false;
flag[1]   = false;
turn;

当时不明白这个算法关键是其中的一句话很迷惑不知道怎么理解

while (flag[j] == true && turn == j);

恩,就是这个很坑,为什么进程j想要进去并且也轮到它了它会在一直死循环等待呢?

 flag[i] = false;

还有这个,为啥在j死循环之后,进程i又给设置不想进入临界区了?

经过多方查找资料之后,终于明白了,关键在于这个Peterson算法是两个进程啊!所以也要有两个这样的循环,接下来是重点:

注意到如果进程P0和P1并发,那么两者中必然会有一个会被while堵塞住(因为flag[0和1]均等于true),而另一个会完成自己的任务并置对方的flag位为false,这时while的条件不再满足,即可执行自己的程序,实现了互斥。

这两个进程是互相影响的,互相给对方设置flag,也就是说,程序应该是这样的:

P0: flag[0] = true; //P0举手表示想进入                       P1:flag[1] = true;

    turn = 1; //表示轮到P1,P1具有访问权限                       turn = 0;
    while (flag[1] == true && turn == 1)                      while (flag[0] == true && turn == 0)
    {                                                         {
        // busy wait                                               //busy wait
    } //P1想进并且能进但是会陷入死循环等待                         }
    // critical section   //P0进入临界区                        // critical section
    flag[0] = false;                                          flag[1] = false;
    // end of critical section                                // end of critical section


其中的关键是下面这句话

flag[0] = false; //P0执行完之后会设置flag值为false目的是让P1进程中的P0可以跳出死循环进而进入临界区执行任务
以此类推

flag[1] = false; //P1执行完之后会设置flag值为false目的是让P0进程中的P1可以跳出死循环而进入临界区执行任务

恩,就是这样的,在P0进程中,如果P0想进入临界区,那么会优先让P0进入临界区而P1死循环等待,直到P1的进程中P1执行完任务并把flag值设置为false之后,P0进程中的P1才能被释放进入临界区从而执行任务。它们是这样互相影响的......(个人理解,不一定对)


接下来再说一下

Dekker算法

Dekker互斥算法是由荷兰数学家Dekker提出的一种解决并发进程互斥与同步的软件实现方法。

参数说明

两个全局共享的状态变量flag[0]和flag[1],表示临界区状态及哪个进程想要占用临界区,初始值为0。

全局共享变量turn(值为1或0)表示能进入临界区的进程序号,初始值任意,一般为0。

算法原理

设有进程P0和P1,两者谁要访问临界区,就让对应的flag=true(例如P0要访问临界区,就让flag[0]=true),相当于“举手示意我要访问”。初始值为0表示一开始没人要访问

trun用于标识当前允许谁进入,turn=0则P0可进入,turn=1则P1可进入。

1)P0的逻辑

do{

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再举手

}

}

visit();// 访问临界区

turn = 1;// P0访问完了,把轮次交给P1,让P1可以访问

flag[0]=false;// P0放下手

2)P1的逻辑

do{

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再举手

}

}

visit();// 访问临界区

turn = 0;// P1访问完了,把轮次交给P0,让P0可以访问

flag[1]=false;// P1放下手


接下来的内容引自:http://www.cnblogs.com/zhengruin/p/4994188.html

Peterson算法与Dekker算法解析

进来Bear正在学习巩固并行的基础知识,所以写下这篇基础的有关并行算法的文章。

在讲述两个算法之前,需要明确一些概念性的问题,

Race Condition(竞争条件),Situations  like  this,  where  two  or  more processes  are  reading or writing some shared data and the final result depends on who runs precisely when, are called race conditions.多个进程(线程)读写共享区域(共享文件、共享变量、共享内存等)时,最后的结果依赖于他们执行的相对时间。

Critical Regions(关键区域),That part of the program where the shared memory is accessed.在程序中,访问共享内存的部分。

Mutual exclusion(互斥), that is, some way of making sure that if one process is using a shared  variable or file, the other processes will be excluded from doing the same thing.指在一个进程在使用共享区域时,防止另外的进程做同样的事情。

同样,需要四个条件来找到一个好的解决方案,实现进程(线程)之间的互斥:

Dekker算法与Peterson算法就是用来实现进程或者线程之间的互斥。

Dekker算法:(参考了百度百科上面的Dekker算法解析,还是挺易懂的)

Dekker算法可以用于控制两个进程(线程)之间的同步,如下实现的功能就是专门用于线程的同步:

其中,flag[2]用来表示是否想要使用关键区,turn用来表示具有访问权限的进程ID。(重点看注释,通过注释,挺好理解的哟~

#include
#include
#include
#define true 1
#define false 0
typedef int bool;
bool flag[2];
int turn;
void visit(int num)
{
        sleep(1);
        printf("P%d is visting\n",num);
}
void P0()
{
        while(true)
        {
                flag[0] = true;//P0想使用关键区。
                while(flag[1])//检查P1是不是也想用?
                {
                        if(turn == 1)//如果P1想用,则查看P1是否具有访问权限?
                        {
                                flag[0] = false;//如果有,则P0放弃。
                                while(turn == 1);//检查turn是否属于P1。
                                flag[0] = true;//P0想使用。
                        }
                }
                visit(0); //访问Critical Partition。
                turn = 1; //访问完成,将权限给P1。
                flag[0] = false;//P0结束使用。
        }
}
void P1()
{
        while(true)
        {
                flag[1] = true; //P1想使用关键区。
                while(flag[0]) //检查P0是不是也想用?
                {
                        if(turn == 0) //如果P0想用,则查看P0是否具有访问权限?
                        {
                                flag[1] = false; //如果有,则P1放弃。
                                while(turn == 0); //检查turn是否属于P1。
                                flag[1] = true; // P1想使用。
                        }

                }
                  visit(1); //访问Critical Partition。
                turn = 0; //访问完成,将权限给P0。
                flag[1] = false; //P1结束使用。
        }
}

void main()
{
        pthread_t t1,t2;
        flag[0] = flag[1] = false;
        turn = 0;
        int err;
        err =  pthread_create(&t1,NULL,(void*)P0,NULL);
        if(err != 0) exit(-1);
        err = pthread_create(&t2,NULL,(void*)P1,NULL);
        if(err != 0 ) exit(-1);
        pthread_join(t1,NULL);
        pthread_join(t2,NULL);
        exit(0);
}

如上所示,如果flag数组和turn都没有符合使用关键区的条件的时候,是不可能进入关键区的。

Peterson算法:

Peterson算法也是保证两个进程(线程)实现互斥的方法,比之前的Dekker算法更加简单,他同样提供了两个变量,保证进程不进入关键区,一个是flag[2],一个是turn,两者的表达意思也类似,flag数组表示能否有权限使用关键区,turn是指有访问权限的进线程ID。(注释很重要,帮助你理解

#include
#include
#include
#define true 1
#define false 0
typedef int bool;
bool flag[2];
int turn;
void procedure0()
{
        while(true)
        {
                flag[0] = true;
                turn = 1;
                while(flag[1] && turn == 1)//退出while循环的条件就是,要么另一个线程
//不想要使用关键区,要么此线程拥有访问权限。
                {
                        sleep(1);
                        printf("procedure0 is waiting!\n");
                }
                //critical section
                flag[0] = false;
        }
}
void procedure1()
{
        while(true)
        {
                flag[1] = true;
                turn = 0;
                while(flag[0] && turn == 0)
                {
                        sleep(1);
                        printf("procedure1 is waiting!\n");
                }
                //critical section
                flag[1] = false;
        }
}
void main()
{

        pthread_t t1,t2;
        flag[0] = flag[1] = false;
        int err;
        turn = 0;
        err =  pthread_create(&t1,NULL,(void*)procedure0,NULL);
        if(err != 0) exit(-1);
        err = pthread_create(&t2,NULL,(void*)procedure1,NULL);
        if(err != 0 ) exit(-1);
        pthread_join(t1,NULL);
        pthread_join(t2,NULL);
        exit(0);
}

Bear将turn的赋值放在while循环的后面,然后main函数中赋初值,也是可行的。


就这么凑和着理解吧。






你可能感兴趣的:(操作系统)