今天在思考之前实习的时候,如何在保证顺序的前提下尽可能达到高并发。之前和同事说过想要通过加锁,被提示效率很低。然后今天我就来看了看锁的实现算法,还是学到了很多东西的。
基本问题
有两个进程P0和P1,需要互斥地访问一段临界区,这里用visit函数来表示。如何控制P0和P1使得满足互斥性?
简单求解
一个简单的想法是,搞一个长度为2的数组,标记本进程是否需要进入临界区。然后如果检测到另外一个进程要进入临界区了,我就循环等待他出去。代码如下:
bool state[2];
void P0(){
state[0] = true;
while(state[1]){
}
visit();
state[0] = false;
}
void P1(){
state[1] = true;
while(state[0]){
}
visit();
state[1] = false;
}
上述算法可以保证两个进程只有一个能进临界区:如果Pi进入了临界区,那么state[Pi]一定已经是true。由于Pi和Pj在临界区内不会改变state的值,所以Pj一定会被阻塞在循环处,直到Pi执行完visit后重置state。
有没有问题呢
上述解法实际上是有个巨大的bug的:如果两个进程同时改变了state,可能他们会被同时阻塞在while的门口。这样,就会造成死锁。
为了避免这个问题,需要引入一个公共变量来代表先来后到。这个公共变量某一时刻只会是一个值,用于指示当state被同时改变时,让谁不等待进入临界区。因此,也要求该变量在某进程进入临界区后,不会被修改。
Peterson算法就是基于这种想法设计的。代码如下:
bool state[2];
int turn = 0;
void P0(){
state[0] = true;
while(state[1] && turn == 1){
}
visit();
state[0] = false;
turn = 1;
}
void P1(){
state[1] = true;
while(state[0] && turn == 0){ //由于在同一时刻turn只会是一个值,且在visit执行过程中不会被改变,因此只有一个进程会被阻塞在这里,另外一个则会进入临界区
}
visit();
state[1] = false;
turn = 0;
}
在许多看过的该算法的解析当中,都是说state数组可以看做是一个“进入临界区的意向”,turn变量指示“被允许进入临界区的进程id”。这种理解虽然有道理,但是需要仔细去想一下为什么这样可以保证仅有一个进程进入临界区,并且不会造成同时无限等待。
多进程的扩展
以上仅仅考虑了两个进程的情况。那么多个进程是否while循环那个步骤要检查多个state数组内的元素呢?应该对while循环的条件做何种修改呢?