操作系统学习笔记 2.1 - 进程同步
进程的同步 学习笔记
参考资料:
1. 相关概念
1.1 什么是进程的同步?
首先复习以下进程的5个特征:
1. 动态性
2. 并发性
3. 独立性
4. 异步性
5. 结构性
由于本来进程的特性,当出发并发的时候每个进程之间的进度。没有直接相关性。
会导致结果的不可复现,等问题。 为了协调进程之间的相互制约关系,引入了进程同步的概念
1.2 临界资源、临界区
一次只允许一个进程所用的资源叫做临界资源。 例如打印机一次只能正在执行一个打印任务;
其他的打印作业都处于 阻塞态 等待上个任务释放打印机临界资源
临界区: 访问临界资源的代码段叫做临界区
可以将临界资源的访问过程分成4个部分
1. 进入区:检查资源,设置正在访问的标志
2. 临界区:访问和使用临界资源
3. 退出区:将正在访问的标识消除掉
4. 剩余区:除此以外的剩下部分
同步: 让本来异步的两个或者多个进程,按照特定次序的直接制约关系:
例如 进程A通过缓冲区对进程B提供数据,当缓冲区为空的时候,B会发生阻塞;
一旦A对缓冲区写入了数据,则唤醒进程B,当缓冲区满了以后,A阻塞,一旦B完成读取
则唤醒A进程;
互斥: 两个没有先后的直接制约关系的进程,因为竞争临界资源发生的等待
例如 进程A B 都要使用打印机,A b直接并没有直接制约关系。
某一个作业先获得打印机资源,另外一个资源就需要等待指导前一个进程释放临界资源
为了禁止两个进程同事进入临界区,同步机制应该遵循如下的原理:
- 空闲让进 可以允许一个进程进程立刻进入临界区
- 忙则等待 已经进入临界区的进程 应该让其他试图进入的临界区的进行等待
- 有限等待:确定再有限时间内可以进入临界区,不会出现饥饿状况
- 让权等待: 如果阻塞,释放处理器
软件实现方法:
1. 单标志法
2. 双标志法先检查
3. 双标志法后检查
4. peterson算法 结合了上述方法
硬件实现方法:
1. 中断屏蔽方法
2. 原语 TestAndSet 以及 Swap
信号量与进程同步
整型信号量 S
wait(S)/P(S){
while(S<=0); // handling for S changed by other
S=S-1
}
signal(S)/V(S){
S=S+1; // release the resouce signal;
}
以上的方式虽然实现了,不断的测试等待同步,但是没有实现让权等待的功能
因为当P过程失败的时候,一直处于while循环,忙等,并没有释放CPU
记录型信号量
typedef struct{
int value;
struct process *L;
}semaphore;
wait(semaphore){
S.value--;
if(S.value<0){
add this process to S.L;
block(S.L); //使用原语block阻塞进程,释放cpu
}
}
signal/V(semophore S){
S.value++;
if(S.value<=0){ // 仍然有进程被阻塞
remove a process P from S.L
wakeup(P);
}
}
利用信号量来实行同步 (前驱关系)
例题:
同时有4个进程再进行 P1, P2 ,P3,P4
P2,P3都必须要P1完成后才可以进行
P4需要P2 P3完成后才开始运行
如何再并发中保证他们的前驱关系?
解决方案:
设置好初始的信号量
A1,A2; P1给P2 P3的信号量
B1; P2给P4的信号量, C1,P3给P2的信号量
Process | P | Do | V |
---|---|---|---|
P1() | - | do something ; | V(A1);V(A2); |
P2() | P(A1) | do something | V(B1);} |
P3() | P(A2); | do something ; | V(C1); |
P4() | P(B1); P(C1); | do something ; | - |
利用信号量来实行互斥
例题:
同时有2个进程再进行 P1, P2
使用临界资源 A
如何再并发中保证互斥?
resurce A=1;
P1(){
P(A);
// use A
V(A);
};
P2(){
P(A);
// use A
V(A);
}
管程Monitor与进程同步
相比于信号量实现的进程同步和互斥:
1. 程序员需要自己实现进程互斥
2. 大量定义的分散的同步变量造成系统管理混乱
3. 操作不当产生死锁(下一篇学习)
便产生了一种新的进程同步工具 - 管程;
优点:
1. 保证了进程互斥,无需程序员自己实现
2. 提供了条件变量Condition_Variable 可以让程序员灵活的实现进程同步
利用共享数据结构抽象的表示系统中的共享资源;并且由对此数据结构实时操作
的一组对应过程组成的资源管理程序,称之为管程;
组成:
- 管程名字
- 共享结构数据说明
- 对其操作的过程函数
- 对于内部设置初始值语句
其表现结构非常像 面向对象中的一个类
monitor demo{
shared_resource S;
condition x;
init_code(){S=5; //初始资源数}
take_away(){S--;
if(S<=0)x.wait;
operations;}
give_back(){S++;operations;if(进程在等待)x.signal;}
}
名称 | 相同点 | 不同点 |
---|---|---|
信号量 | PV操作 | 有值,显示剩余资源 |
条件变量 | wait/signal | 进程阻塞唤醒,配合管程管理剩余资源 |
案例:哲学家进餐问题
题目:
一个圆桌有5个哲学家,每两个哲学家之间有一根筷子
哲学家只有两种状态:吃饭 思考
只有当哲学家饥饿时候才试图拿起左右的两根筷子
如果筷子已经再别人手上,等待别人。
只有拿到两根筷子才进行进食, 放下筷子继续思考
目标:
- 防止饥饿
- 防止死锁
最开始,按照题意构建的进程如下:
Piloshopher(int i)
do{
P(chopstick[i]);
P(chopstick[(i+1)%5]);
eat;
Vchopstick[i]);
Vchopstick[(i+1)%5]);
think;
}while(1);
}
但是上面的过程有一个问题,当哲学家同时饿了,同时开始拿起左边的筷子,会导致所有的哲学家
都只有一根筷子;因此对于他们取筷子应该也做出限制 , 最多一个一个来去筷子;
mutex=1
Piloshopher(int i)
do{
P(mutex);
P(chopstick[i]);
P(chopstick[(i+1)%5]);
V(mutex);
eat;
Vchopstick[i]);
Vchopstick[(i+1)%5]);
think;
}while(1);
}