本次笔记内容:
9.5 临界区
9.6 方法1:禁用硬件中断
9.7 方法2:基于软件的解决方案
9.8 方法3:更高级的抽象
临界区中存在的属性:
采用硬件中断需要考虑时钟中断:时钟中断是控制进程调度的手段之一。
但是,存在如下问题:
例子:假设有两个线程,T0和T1。Ti的通常结构为:
do {
enter section 进入区域
critical section 临界区
exit section 离开区域
reminder section 提醒区域
} while(1);
线程可能共享一些共有的变量来同步他们的行为。下面想设计一种方法,能在有限时间内实现退出/进入页区。
do {
while (turn != i) ; // 如果不是i,死循环,直到turn是i,才可以跳出循环区
critical section // 执行临界区代码
turn = j; // turn赋为j,退出循环
reminder section
} while(1);
上述方法满足互斥,但有时不满足progress。
因此,使用数组flag指示进程是否准备好进入临界区。
对于有线程0、线程1的情况:
do {
while (flag[j] == 1); // 如果另一个进程想进来,此进程先谦让一下,自己先循环着
flag[1] = 1; // 如果别的进程未准备,则自己赋成1,表示自己要进入临界区
critical section
flag[i] = 0;
reminder section
} while(1);
但是上述方法没有互斥。因此考虑将flag[i] = 1前置,代码如下:
do {
flag[1] = 1;
while (flag[j] == 1);
critical section
flag[i] = 0;
reminder section
} while(1);
上述代码满足互斥,但是存在死锁,进程0、1都可能flag为0,谁都跳不出循环。
满足进程Pi和Pj之间互斥的经典的基于软件的解决方法(1981年),Use two shared data items(用上了turn和flag)。
int turn; // 指示该谁进入临界区
boolean flag[]; // 指示进程是否准备好进入临界区
Code for ENTER_CRITICAL_SECTION
flag[i] = TRUE;
turn = j;
while(flag[j] && turn == j);
Code for EXIT_CRITICAL_SECTION
flag[i] = FALSE;
对于进程Pi的算法:
do {
flag[i] = TRUE;
turn = j;
while (flag[j] && turn == j);
CRITICAL SECTION
flag[i] = FALSE;
REMAINDER SECTION
} while (TRUE);
上述算法能够满足互斥、前进、有限等待三种特性。可以用反证法来证明。
dekker算法的实现如下。
flag[0] := false flag[1] := false := 0 // or 1
do {
flag[i] = TRUE;
while flag[j] == true {
if turn != i {
flag[i] := false
while turn != i {}
flag[i] := TRUE
}
}
CRITICAL SECTION
turn := j
flag[i] = FALSE;
REMAINDER SECTION
} while (TRUE);
此算法特征在课内不展开讨论。
基本思路:对于i进程,如果前面有进程,那么i进程就等待;对于i后面的进程,则等待i。这整体是一种循环。
N个进程的临界区:
一个二进制状态(锁定/解锁),两种方法:
使用锁来编写临界区
前面的例子变得简单起来:
lock_next_pid->Acquire();
new_pid = next_pid++;
lock_next_pid->Release();
特殊的原子操作如:
上述两个指令代码如下:
boolean TestAndSet(boolean *target) {
boolean rv = *target;
*target = TRUE;
return rv;
}
void Exchange(boolean *a, boolean *b) {
boolean temp = *a;
*a = *b;
*b = temp;
}
上述两个指令已经被分别封装为一条机器指令,执行过程中不允许被中断。
如上图,在锁的方法中,应用原子操作,对描述状态的value进行操作。
如上图,该方法存在进程的忙等情况,该如何改进呢?
可以应用睡眠的方法。
无忙等待的锁实现如下:
class Lock {
int value = 0;
WaitQueue q;
}
Lock::Acquire() {
while (test-and-set(value) {
add this TCB to wait queue q;
schedule();
}
}
// 如果跳不出去,继续睡眠
Lock::Release() {
value = 0;
remove one thread t from q;
wakeup(t);
}
如果临界区执行时间短,选择忙等方式,因为上下文切换也较大;如果临界区执行时间长,选择无忙等方式。
对于线程Ti的代码:
int key;
do {
key = 1;
while (key == 1) exchange(lock, key);
critical section
lock = 0;
remainder section
} while(1);
优点:
缺点:
三种方法要点总结如上图。