互斥访问遵循的原则
- 空闲让进
- 忙着等待
- 有限等待
- 让权等待
单标志法
int turn = 0; //turn 表示当前允许进入临界区的进程号
P0:
while(turn != 0); //进入区
critical section; //临界区
turn = 1; //退出区
remainder section; //剩余区
P1:
while(turn != 1);
critical section;
turn = 0;
remainder section;
- 算法思想
- 两个进程在访问玩临界区之后会把使用临界区的权限转交给另一个进程。也就是说每个进程进入临界区的权限由另一个进程赋予。
- 访问顺序
- P0 -> P1 -> P0 -> …
- 违背空闲让进原则
- 如果此时允许进入临界区的进程是P0, 而P0一直不访问临界区,那么虽然此时临界区空闲,但是不允许P1访问。
双标志先检查法
bool flag[2]; //表示进入临界区意愿的数组
flag[0] = false;
flag[1] = false; //刚开始设置为两个进程都不想进入临界区
P0:
while(flag[1]); //进入区
flag[0] = true; //标记P0想要进入临界区
critical section; //临界区
flag[0] = false; //标记P0不想进入临界区
remainder section; //剩余区
P1:
while(flag[0]);
flag[1] = true;
critical section;
flag[1] = false;
remainder section;
- 算法思想
- flag数组中各个元素标记各进程是否想要进入临界区。进程i在进入临界区之前先检查有无别的进程已经进入,如果没有,将flag[i]=true,然后开始访问。
- 违背忙则等待原则
- 按照6->13->7->14->8->15的顺序执行时,P0和P1将会同时访问临界区。原因在于,进入区的"检查"和"上锁"两个处理不是一气呵成的。"检查"后,"上锁"前可能发生进程切换。
双标志后检查法
bool flag[2]; //表示进入临界区意愿的数组
flag[0] = false;
flag[1] = false; //刚开始设置为两个进程都不想进入临界区
P0:
flag[0] = true; //标记P0想要进入临界区
while(flag[1]); //进入区
critical section; //临界区
flag[0] = false; //标记P0不想进入临界区
remainder section; //剩余区
P1:
flag[1] = true;
while(flag[0]);
critical section;
flag[1] = false;
remainder section;
- 算法思想
- 先上锁后检查
- 违背空闲让进和有限等待原则
- 按照6->13->7->14->8->15的顺序执行时,P0和P1将flag都置为true,P0和P1都无法进入临界区,产生饥饿现象。
需 要 分 析 哪 一 个 部 分 是 进 入 区 , 在 进 入 区 做 了 什 么 事 情 , 如 果 做 了 两 三 件 事 情 , 则 要 分 析 异 步 执 行 时 会 不 会 引 发 问 题 。 \color{red}{需要分析哪一个部分是进入区,在进入区做了什么事情,如果做了两三件事情,则要分析异步执行时会不会引发问题。} 需要分析哪一个部分是进入区,在进入区做了什么事情,如果做了两三件事情,则要分析异步执行时会不会引发问题。
Peterson算法
bool flag[2]; //表示进入临界区意愿的数组,初始为false
int turn = 0;
P0:
flag[0] = true; //标记P0想要进入临界区
turn = 1; //可以让对方优先进入临界区
while(flag[1] && turn == 1); //进入区
critical section; //临界区
flag[0] = false; //标记P0不想进入临界区
remainder section; //剩余区
P1:
flag[1] = true;
turn = 0;
while(flag[0] && turn == 0);
critical section;
flag[1] = false;
remainder section;
-
算法思想
- 如果双方都想进入临界区,可以让进程主动让对方使用临界区(后让的那个会循环等待)。
-
未遵循让权等待原则,没有阻塞排队机制
- 等待的那个进程仍会占用CPU
进程互斥的硬件实现方法
中断屏蔽
...;
关中断; //关中断后即不允许当前进程被中断
临界区;
开中断;
...;
- 缺点:不适用于多处理机。
TestAndSet指令
- 也称为TSL指令(TestAndSetLock)
以下是C语言描述的逻辑:
//这只是对它的逻辑的描述,它是原子级的操作。
//bool型共享变量lock表示当前临界区是否被加锁
//true表示已加锁
bool TestAndSet(bool *lock){
bool old;
old = *lock; //old存放lock原来的值
*lock = true; //无论之前是否已经加锁,都将lock设为true
return old; //返回lock原来的值
}
while(TestAndSet(&lock));//上锁并检查
临界区;
lock = false;
剩余区;
- 适用于多处理机环境,无需检查是否有逻辑漏洞。
- 不满足让权等待原则。
Swap指令
- 也叫做Exchange指令,或者XCHG指令
//Swap指令的作用是交换两个变量的值
Swap(bool *a, bool *b){
bool temp;
temp = *a;
*a = *b;
*b = temp;
}
bool old = true;
while(old == true)
Swap(&lock, &old);
临界区;
lock = false;
剩余区;
信号量机制
1、在双标志先检查法中,进入区的"检查"、“上锁"操作无法一气呵成,从而导致了两个进程有可能同时进入临界区的问题。
2、所有的解决方案都无法实现"让权等待”。
信号量其实就是一个变量,可以表示系统中某种资源的数量。
原语是一种特殊的程序段,其执行只能一气呵成。原语是由关中断/开中断指令实现的。
一对原语:wait(S)和signal(S)。
- wait(S),signal(S)通常简称为P,V操作。
整型信号量
int S = 1; //初始化整型信号量,表示当前系统中可用的打印机资源数
void wait(int S){ //wait原语,相当于"进入区"
while(S <= 0); //如果资源数不够,就一直循环等待
S = S - 1; //如果资源数够,占用一个资源
}
void signal(int S){ //signal原语,相当于"退出区"
S = S + 1; //使用完资源后,在退出区释放资源
}
进程P0:
...
wait(S); //进入区
使用打印机资源...//临界区
signal(S); //退出区
...
- 存在的问题:不满足让权等待
记录型信号量
/*记录型信号量的定义*/
typedef struct{
int value; //剩余资源数
struct process *L; //等待队列
}semaphore;
/*某进程需要使用资源时,通过wait原语申请*/
void wait(semaphore S){
S.value --;
if(S.value < 0){
block(S.L); //如果剩余资源数不够,使用block源于使进程从运行态进入阻塞态,并挂到信号量S的等待队列(阻塞队列)。
}
}
/*进程使用完资源后,通过signal原语释放*/
void Signal(semaphore S){
S.value ++;
if(S.value <= 0){
wakeup(S.L);//释放资源后,如果还有别的进程在等待这种资源,使用wakeup原语唤醒等待队列中的一个进程,该进程从阻塞态变为就绪态。
}
}
- S.value的初值表示系统中某种资源的数目。
- 对S的一次P操作意味着请求一个单位的该资源,S.value–
- 因为有阻塞机制,该机制遵循了"让权等待"原则
- 一次V操作意味者释放一个单位的资源,S.value++
信号量机制实现进程互斥
- 分析并发进程的关键活动,划定临界区
- 设置互斥信号量mutex,初值为1
/*信号量机制实现互斥*/
semaphore mutex = 1; //初始化信号量
P1(){
...
P(mutex); //使用临界区需要加锁
临界区...
V(mutex); //使用临界资源后需要解锁
}
P2(){
...
P(mutex); //使用临界区需要加锁
临界区...
V(mutex); //使用临界资源后需要解锁
}
对 不 同 的 临 界 资 源 需 要 设 置 不 同 的 互 斥 信 号 量 \color{red}{对不同的临界资源需要设置不同的互斥信号量} 对不同的临界资源需要设置不同的互斥信号量
P , V 操 作 必 须 成 对 出 现 \color{red}{P,V操作必须成对出现} P,V操作必须成对出现
信号量机制实现进程同步
- 进程同步:要让各并发进程按要求有序的推进
- 设置同步信号量S, 初值为0
- 在"前操作"之后执行V(S)
- 在"后操作"之后执行P(S)
/*信号量机制实现同步*/
semaphore S = 0; //初始化信号量
P1(){
代码1;
代码2;
V(S);
代码3;
}
P2(){
P(S);
代码4;
代码5;
代码6;
}
死锁的检测和解除
- 用某种数据结构来保存资源的请求和分配信息
-
用死锁检测算法化简资源分配图后,还连着边的那些进程就是死锁进程。
-
解除死锁的方法
- 资源剥夺法
- 撤销进程法
- 进程回退法
-
决定对谁动手
- 进程优先级
- 已执行时间
- 剩余时间
- 已经使用了多少资源
- 进程是交互式的还是批处理式的