Peterson算法概述
Peterson算法是一种实现进程/线程间互斥访问临界区的算法。(线程间共享内存地址空间,进程需要采用共享内存实现)
关键术语:
临界区:一段代码,进程/线程在这段代码中进程将访问共享资源,当另外一个进程已在这段代码运行时,其他进程就不能在这段代码中运行。
互斥:当一个进程/线程在临界区访问共享资源时,其他进程/线程不能进入临界区访问任何其他共享资源的情形。
wiki定义:
Peterson‘s algorithm (or Peterson‘s solution) is a concurrent programming algorithm for mutual exclusion that allows two or more processes to share a single-use resource without conflict, using only shared memory for communication. It was formulated by Gary L. Peterson in 1981. While Peterson‘s original formulation worked with only two processes, the algorithm can be generalized for more than two.
Peterson算法实现
该算法使用两个变量,flag
和 turn
。 flag[n]
值为 true
表示进程 n
想要进入临界区。turn表示现在轮到谁,是一个进程编号。
int flag[2];
int turn;
void init() {
flag[0] = flag[1] = 0; // 1->thread wants to grab lock
turn = 0; // whose turn? (thread 0 or 1?)
}
void lock() {
flag[self] = 1; // self: thread ID of caller
turn = 1 - self; // make it other thread‘s turn
while ((flag[1-self] == 1) && (turn == 1 - self))
; // spin-wait
}
void unlock() {
flag[self] = 0; // simply undo your intent
}
算法解释:
flag[self] = 1
:设置自己进程感兴趣,想要访问临界区。
turn = 1 - self
:将turn设置为对方进程。注意这个turn是个共享变量,若多进程/多线程进行访问,会保留最后一次写的turn值,前面写的值被写覆盖了(overwriting)。然后是一个自旋等待(CPU空转,忙等待,busy wait):while ((flag[1-self] == 1) && (turn == 1 - self));
对该自旋等待真值表进行分析,如下:
flag[1-self] == 1 | turn == 1 - self | 真值 | 含义 |
---|---|---|---|
T | T | T | 对方进程也想访问临界区,且turn值为自己设定 |
T | F | F | 对方进程也想访问临界区,且turn值为对方设定 |
F | T | F | 只有自己想访问临界区,直接访问即可。 |
F | F | F | 只有自己想访问临界区,直接访问即可。 |
由上述真值表可见,仅有双方(两个进程/线程)都想访问临界区时,才会出现自旋情况。将上述情况以单核CPU情况模拟,有如下两种情况。
- P0先写turn值而P1后写:P1自旋,P0进入临界区
Process 0 | Process 1 | turn值 | 事件 |
---|---|---|---|
lock() flag[0] = 1; turn = 1; |
1 | 调度程序调度P0执行 | |
lock() flag[1] = 1; turn = 0; while ((flag[0] == 1) && (turn == 0)); P1自旋 |
0 | 中断,调度程序调度P1执行 | |
进入临界区 do something 出临界区 unlock() flag[0] = 0; |
0 | 中断,调度程序调度P0执行 | |
while ((flag[0] == 1) && (turn == 0)); 自旋结束进入临界区 do something 出临界区 unlock() flag[1] = 0; |
0 | 中断,调度程序调度P1执行 |
- P1先写turn值而P0后写:P0自旋,P1进入临界区
Process 0 | Process 1 | turn值 | 事件 |
---|---|---|---|
lock() flag[1] = 1; turn = 0; |
0 | 调度程序调度P1执行 | |
lock() flag[0] = 1; turn = 1; while ((flag[1] == 1) && (turn == 1)); P1自旋 |
1 | 中断,调度程序调度P0执行 | |
进入临界区 do something 出临界区 unlock() flag[1] = 0; |
1 | 中断,调度程序调度P1执行 | |
while ((flag[1] == 1) && (turn == 1)); 自旋结束进入临界区 do something 出临界区 unlock() flag[0] = 0; |
1 | 中断,调度程序调度P0执行 |
由上述例子可见:并发时,两进程/线程中存在着某种抢占的关系,即谁先写入turn值,就不会因此而自旋(因为自旋条件为turn为对面值);若不是并发,则可以直接进入,而后上锁的进程/线程则需要等待先上锁进程/线程解锁。
算法评价
Peterson算法是一种不依赖硬件实现的锁机制。如今大多数CPU以指令乱序执行来提高执行效率,此时实现Peterson算法就得使用相关内存屏障指令。现在一般使用硬件支持的锁机制(比如test-and-set或compare-and-swap),这些机制往往只需要很少的硬件支持。
reference
[1] wiki
[2] 操作系统导论
[3] 现代操作系统
[4] 深入理解计算机系统