同步和互斥
同步和互斥是进程之间竞争和协作的解决方法(两种进程关系)。
大多数情况下, 同步是建立在互斥的基础之上的. 同步是一种更为复杂的互斥,而互斥是一种特殊的同步。
- 同步是直接制约关系,在异步环境下进行,可以用信号量实现同步。
- 互斥是间接制约关系,满足
- 空闲让进: 独占资源有空, 你才能进入使用
- 忙则等待: 独占资源没空, 你就得等待
- 有限等待: 但我承诺你不会一直等
- 让权等待: 但如果你等待的时候, 请把CPU让出来
互斥的实现
使用互斥锁(互斥量)Mutex实现.
//Swap指令 key= true; while (key != false) Swap(&lock,&key); critical section; lock = false; remainder section;
</TabItem>
</Tabs>
## 同步的实现
### 管程Monitor
```cpp
monitor Demo{
sharing_struct S;//系统中某种资源定义的共享数据结构
condition x;//阻塞原因条件变量,保存一个等待队列.实现同步
condition y;//多个条件变量的设置
init_code(){
S = 5;
}
take_away(){
if(S<=0) x.wait(); //资源不够,在x上阻塞等待
--S;
}
give_back(){
++S;
if(x.hasNext) x.signal();//唤醒一个阻塞进程
}
};
- 共享资源的操作封装起来,管程内的共享数据结构只能被管程内的过程所访问
- 每次仅允许一个进程进入管程,从而实现进程互斥
- 使用条件变量这种同步机制
信号量Semaphore(PV)
// 整型信号量
wait(S){
while(S<=0);
S = S - 1;
}
signal(S){
S = S + 1;
}
//记录型信号量
typedef struct{
int value;
struct process *Q;//阻塞进程的排队链表
} semaphore;
void wait(semaphore S){
S.value --;
if(S.value <0){
block(S.Q);//运行态进入阻塞态
add this process to S.Q;
}
}
void signal(semaphore S){
S.value ++;
if(S.value<=0){
remve a process P from S.Q;
wakeup(P);//唤醒阻塞队伍中的第一个进程
}
}
//同步实现
semaphore S = 0;
P1(){
x;
V(S);
}
P2(){
P(S);
y;
}
// 互斥实现
semaphore S = 1;
P1(){
P(S);
critical section;
V(S);
}
P2(){
P(S);
critical section;
V(S);
}
经典同步问题的处理方法
- 生产者和消费者问题
- 申请资源P操作一定在互斥加锁P操作之前,否则会导致死锁。
- 生产者消费者问题中如果生产者和消费者都是唯一,mutex互斥信号量可以不要。
- 读者写者问题:写者优先、读者优先、顺序访问
- 哲学家进餐问题:死锁问题解决:
- 最多允许n-1个同时进餐
- 仅当一个哲学家左右两边的筷子同时可用,才会拿起筷子
- 哲学家编号,奇数先拿左筷子,偶数先拿右筷子
//生产者消费者问题
Semaphore filledBuffer = 0;
Semaphore emptyBuffer = n;
Semaphore mutex = 1;
//读者写者问题
Semaphore mutex = 1; //read or write mutex
Semaphore rmutex = 1; //change count mutex in readers
Semaphore wmutex = 1; //顺序锁,当有writer等待读写锁时,后来的reader不再进入count
int count = 0;
//哲学家进餐问题
semaphore fork[5] = {1,1,1,1,1};
具体注意:
- PV成对出现
- PV操作要分别紧贴临界区的头尾部,临界区的代码要尽量短,不能有死循环
- 用于互斥的信号量通常设为1.
死锁
- 参与死锁的进程至少有两个
- 每个参与死锁的进程均等待资源
- 参与死锁的进程中至少有两个进程占有资源
- 死锁进程是系统中当前进程集合的一个子集
死锁产生的原因
- 不可剥夺性系统资源不足
- 进程推进顺序不当
- 信号量使用不当
死锁产生的必要条件
- 互斥: 多个进程请求访问的资源是独占的,需要互斥访问
- 不剥夺: 不能剥夺访问
- 请求与保持: 等待的时间中不会放弃已有的资源
- 环路等待: 我等你拥有的资源, 你等我的
死锁预防
会对系统并发性产生很大的副作用
破坏4个必要条件之一:
- 破坏互斥条件:不太可能,资源本身的属性限制
- 破坏不剥夺条件:获得了某些资源的进程,若新的资源请求不能立即满足,必须释放所有已有的资源。造成之前一段工作失效,重复申请和释放资源增加系统开销,降低系统吞吐量。通常不用于代价较大的场合
- 破坏请求保持条件:预先静态分配方法,一次性申请所有资源,没有满足前不投入运行。简单安全,但是降低了资源利用率。导致其它进程饥饿
- 破坏环路条件:有序资源分配方法,资源编号。确定每个进程必须按编号递增的顺序请求资源。同类资源一次申请完。
死锁避免:银行家算法
将系统的状态分为安全状态和不安全状态,系统进行资源分配前先计算资源分配的安全性,若此次分配不会导致系统进入不安全状态,则分配,否则等待。若某一刻,系统能按某种顺序为每个进程分配所需的资源,直至最大需求,使每个进程都可以顺利完成,则此时的系统状态为安全状态,该序列为安全序列。不存在一个该序列,则此时的系统状态为不安全状态。
银行家算法:
- Max matrix最大需求矩阵
- Allocation matrix 分配矩阵
- Need matrix需求矩阵
- Available vector 可利用资源向量
- 安全性算法:
- work :=
available - finish:=
{true,false}[]
- work :=
死锁检测和解除
- 资源分配图:
- 死锁定理:资源分配图的简化-删掉请求可获取的进程边
- 检测时间:每次分配后,但算法复杂,系统开销大,选取比较长的时间间隔来执行。
- 解除方法:剥夺资源-撤消进程-进程回退