1、进程互斥的软件实现方法
- 单标志法
- 双标志法
- 双标志后检查
- Peterson算法
2、单标志法
算法思想:两个进程在访问完临界区后会把使用临界区的权限转交给另一个进程,也就是说每个进程进入临界区的权限只能被另一个进程赋予;
通过代码来分析,在系统中会设置一个turn的变量:
int turn = 0; //表示当前允许进入临界区的进程号;
默认turn的初始值为0,刚开始可以由0号进程 P 0 P_0 P0进入临界区进行访问;
P 0 P_0 P0和 P 1 P_1 P1对临界区进行访问的代码分别是:
# p0
while (turn !=0); (1) // 进入区
critical section; (2) // 临界区
turn = 1; (3) // 退出区
remainder section; (4) // 剩余区
# p1
while (turn !=1); (5) // 进入区
critical section; (6) // 临界区
turn = 0; (7) // 退出区
remainder section; (8) // 剩余区
turn的初值为0,即刚开始只允许0号进程 P 0 P_0 P0进入临界区;如果刚开始是 P 1 P_1 P1进程, P 1 P_1 P1进程在进入区进行判断时,turn=0,因为while(turn != 1)循环条件满足,所以 P 1 P_1 P1进程会一直在循环语句中循环,一直到分配给 P 1 P_1 P1的时间片用完,发生调度,将 P 0 P_0 P0进程放到处理机上运行, P 0 P_0 P0进程执行while(turn!=0)不符合循环条件,所以会跳过循环,进入临界区,访问临界区;在 P − 0 P-0 P−0访问临界区时发生进程调度调用 P 1 P_1 P1时,由于turn=0,所以进程 P 1 P_1 P1依然无法进入临界区;
一直到 P 0 P_0 P0访问完临界区之后,会在退出区中修改turn的值,把turn修改为turn=1,把访问临界区的权限交给进程 P 1 P_1 P1;这时候当调度 P 1 P_1 P1进程时, P 1 P_1 P1可以顺利进入临界区,当 P 1 P_1 P1进入退出区时,重新把turn修改为turn=0;
通过以上分析,我们可以知道该算法可以实现“同一时刻最多只允许一个进程访问临界区”;
单标志法中,turn表示当前允许进入临界区的进程号,而只有当前允许进入临界区的进程在访问了临界区之后,才回修改turn值。也就是说,对于临界区的访问,一定是按照 p 0 − > p 1 − > p 0 − > p 1 − > . . . p_0->p_1->p_0->p_1->... p0−>p1−>p0−>p1−>...这样轮流访问的。
这种必须“轮流访问”带来的问题是,如果此时允许进入临界区的进程是 P 0 P_0 P0,而 P 0 P_0 P0一直不访问临界区,那么虽然此时临界区空闲,但是并不允许 P 1 P_1 P1访问;
因此,单标志法存在的主要问题是:违背“空闲让进”原则;
3、双标志先检查法
算法思想:设置一个布尔型数组flag[],数组中各个元素用来标记各进程想进入临界区的意愿,比如"flag[0]=True"意味着0号进程 P 0 P_0 P0现在想要进入临界区。每个进程在进入临界区之前先检查当前有没有别的进程想进入临界区,如果没有,则把自身对应的标志flag[i]设置为True,之后开始访问临界区;
bool flag[2]; // 表示进入临界区意愿的数组
flag[0] = False;
flag[1] = False; // 刚开始设置为两个进程都不想进入临界区
假设此时系统中只有两个进程,两个进程的代码如下:
bool flag[2]; // 表示进入临界区意愿的数组
flag[0] = false;
flag[1] = false; // 刚开始设置为两个进程都不想进入临界区
# p0进程
while (flag[1]); (1)
flag[0] = true; (2)
critical section; (3)
flag[0] = false; (4)
remainder section;
# p1进程
while (flag[0]); (5) // 如果此时P0想进入临界区,P1就一直循环等待
flag[1] = true; (6) // 标记为P1进程想要进入临界区
critical section (7) // 访问临界区
flag[1] = false; (8) // 访问完临界区,修改标记为P1不想使用临界区
remainder section;
分析一下 P 1 P_1 P1进程,如果 P 1 P_1 P1进程想要访问临界区,首先需要知道 P 0 P_0 P0是否想要进入临界区,只有当flag[0]为False时 P 1 P_1 P1程序才能跳出循环;当 P 1 P_1 P1进程确定了其它进程确实不想访问临界区之后, P 1 P_1 P1会把自己是否想要进入临界区的标志位设置为True,向其它进程表明 P 1 P_1 P1进程想要访问临界区,当访问临界区之后, P 1 P_1 P1进程将自己的标志位设置为False,告诉其它进程当前进程已经用完临界区;
但是该方法有一个问题,上述两个进程是并发执行的,并发执行会存在一个很大的问题——异步性;如果 P 0 P_0 P0和 P 1 P_1 P1在执行过程中发生了切换,刚开始是进程 P 0 P_0 P0在处理机上运行,首先运行代码语句(1),因为刚开始两个进程进入临界区的意愿都为False,刚开始执行(1)会发现 P 1 P_1 P1进程暂时不想进入临界区,则进程 P 0 P_0 P0会执行代码语句flag[0]=True,恰好在这个阶段发生进程切换,切换到进程 P 1 P_1 P1, P 1 P_1 P1进处理机执行语句(5),此时进程 P 1 P_1 P1发现0号进程其flag[0]为False,因此 P 1 P_1 P1进程顺利跳出循环往下执行;
若按照(1)(5)(2)(6)(3)(7)的顺序执行,则 P 0 P_0 P0和 P 1 P_1 P1有可能同时访问临界区;因此,双标志监察法的主要问题是:违反“忙则等待”原则;当一个进程在访问临界区时,另一个进程也可能在访问临界区;
发生上述问题的原因在于,进入区的“检查”和“上锁”两个处理不是一气呵成的。“检查”后,“上锁”前可能发生进程切换;
为了解决两个进程不能互斥的问题,提出了“双标志后检查法”;
4、双标志后检查法
算法思想:双标志先检查法的改版。前一个算法的问题是先“检查”后“上锁”,但是这两个操作又无法一气呵成,因此导致了两个进程同时进入临界区的问题。因此,人们又想到先“上锁”后“检查”的方法来避免上述问题;
其代码如下所示:
bool flag[2];
flag[0] = False;
flag[1] = False;
# P0进程
flag[0] = true; (1)
while (flag[1]); (2)
critical section; (3)
flag[0] = false; (4)
remaider section;
# P1进程
flag[1] = true; (5) // 标记为P1进程想要进入临界区
while(flag[0]); (6) // 如果P0也想进入临界区,则P1循环等待;
critical section; (7) // 访问临界区;
flag[1] = false; (8) // 访问完临界区,修改标记为P1不想使用临界区;
remaider section;
但是上述方法同样存在问题,若按照(1)(5)(2)(6)…的顺序执行,则 P 0 P_0 P0和 P 1 P_1 P1都无法进入临界区;
因此,双标志后检验法虽然解决了“忙则等待”的问题,但是又违背了“空闲让进”和“有限等待”的原则,会因各进程都长期无法访问临界资源而产生“饥饿”现象;两个进程都争着想进入临界区,但是谁也不让谁,最后谁都无法进入临界区;
5、Peterson算法
算法思想:双标志后检查法中,两个进程都争着想进入临界区,但是谁也不让谁,最后谁都无法进入临界区。Gary L.Peterson想到了一种方法,如果双方都争着想进入临界区,那可以让进程尝试“孔融让梨”,主动让对方先使用临界区;
其代码如下所示:
bool flag[2];
int turn = 0;
# P0 进程
flag[0] = true; (1)
turn = 1; (2)
while (flag[1] && turn==1); (3)
critical section; (4)
flag[0] = false; (5)
remainder section;
# P1 进程:
flag[1] = true; (6) // 表示自己想进入临界区
turn = 0; (7) // 可以优先让对方进入临界区
while (flag[0] && turn==0); (8) // 对方想进,且最后一次是自己让梨,那自己就循环等待
critical section; (9) //
flag[1] = false; (10) // 访问完临界区,表示自己已经不想访问临界区了
remainder section;
两种双标志法的问题都是由于进入区的几个操作不能一气呵成导致的。我们可以推理验证在Peterson算法中,两个进程进入区中的各种操作按不同的顺序穿插执行会发生什么情况:
- 按(1)(2)(3)(6)(7)(8)…执行,执行过程类似于串行的过程, P 0 P_0 P0优先进入临界区;
- 按(1)(6)(2)(3)…执行, P 0 P_0 P0和 P 1 P_1 P1都表示自己想进入临界区;切换回 P 0 P_0 P0进程, P 0 P_0 P0进程会表示自己愿意优先把进入临界区的权力让给 P 1 P_1 P1进程,也就是设置turn=1; P 0 P_0 P0继续往下执行,因为flag[1]=true同时目前turn==1,因此 P 0 P_0 P0会陷入while循环直到时间片用完;接着时间片分配给进程 P 1 P_1 P1,执行turn=0和while循环,进程 P 1 P_1 P1会陷入循环,当时间片用完切换到 P 0 P_0 P0进程,此时因为turn=0,所以 P 0 P_0 P0进程会跳出while循环,执行下面的语句;
Peterson算法用软件方法解决了进程互斥问题,遵循了空闲让进、忙则等待、有限等待三个原则,但是依然为遵循让权等待的原则;