临界区问题:一种新的视角
首先阐明两个概念:
临界资源:必须互斥访问的资源(如公共变量,打印机等)。
临界区:访问临界资源的一段代码。
对于一个进程Pi,其典型结构如下:
do
{
进入临界区
临界区 ; 访问临界资源的代码
退出临界区
剩余区 ; 其余的代码
}while(1);
考虑两个进程P0, P1并发执行。如何保证它们互斥访问临界资源呢?
前人提出了3种算法。
算法1:
让P0,P1共享一个bool变量turn。turn取值为0或1。
若turn == i, 则允许Pi在临界区内执行。
Pi的结构如下:
do
{
While (turn != i) ; // 若turn != i,循环等待
临界区
turn = j;
剩余区
}while(1);
这种算法能够保证互斥性,但它要求P0,P1在临界区中的执行是严格交替的。如果turn=0,且P1要进入临界区,那么尽管P0可能在其剩余区内,P1并不能进入。
算法2
引入一个bool数组flag[2];数组元素初始化为false。如果flag[i]=true,表明Pi准备进入临界区。
Pi结构如下:
do
{
flag[i] = true;
while (flag[j]) ; // 若flag[j] == true,则循环等待
临界区
flag[i] = false;
剩余区
}while(1);
该算法中,Pi首先置flag[i]为真,表明它准备进入临界区,接着,它检查并验证Pj没有准备进入临界区。但这个算法仍然不行,如果flag[0] = flag[1] = true,则两个进程会无限等待下去(具体分析省略)。
算法3
将算法1和算法3结合起来:
bool flag[2];
int turn;
初始时,flag[0] = flag[1] = false;
Pi结构如下:
do
{
flag[i] = true;
turn = j;
while(flag[j] && turn == j) ;
临界区
flag[i] = false;
剩余区
}while(1);
可以证明这个解答是正确的。
下面,我从“礼”的角度来分析这3个算法。
临界区问题即要求进程能够互斥访问临界资源。系统的实现决定了要实现这个目标,必须进程“自觉”遵守相关规定(当然,可以依靠操作系统的某些机制)。
“自觉”就要讲“礼”。
算法1只是使用一个turn变量,而算法2使用了一个数组。这个数组分别记录了两个进程各自的“意愿”。每个进程在试图进入临界区的时候,都先“体察”一下对方的“意愿”,这就懂得了“礼让”。
但算法2仍然不能解决问题。为什么呢?从中国传统的“礼”来说,中国人喜欢“假客气”。例如别人对你说:“吃个苹果吧。”你虽然想吃,但你通常会说:“不用了。”只有别人把苹果“硬塞”给你,你就手下了(因为你确实想吃)。除非你真的是讨厌苹果,否则你不会拒绝。
所以,在“体察”对方“意愿”的时候,可能会获得不正确的结果。
算法3的高明之处在于,先将turn设置成对方。然后在看对方的“意愿”。这就像:我先把苹果“硬塞”给对方,然后看他是否接受。如果他心里想要,就会接受。这时候你就知道了他真实的意愿。如果他确实不想吃,那么他就会推脱。
在这种方式下,你“体察”到的“意愿”是真实的,而不是经过掩饰的虚假。
从“礼”的角度来分析,至少有3点好处:
1. 理清算法的流程。
2. 延伸算法的思想内涵。
3. 对设计新的算法提供了思路(例如,经过研究现实生活中“礼”的形式,提出更优化的算法)
By 夏至逃亡 @TITAN Studio
程序员的路是一行一行走出来的,不管多困难,我能做的只是,绝不低头。