回顾:
-
信号量
- 对应一种资源 信号量的value:
- 资源的剩余数量
- 如果小于0表示有进程在等待该资源 P操作:
- 申请一个资源
- 如果资源不够就阻塞该进程 V操作
- 释放一个资源
- 并检查是否仍有进程等待该资源, 有则唤醒一个进程
实现进程互斥
步骤
-
确定临界区
即确定哪段代码用于访问临界资源 -
设置 互斥信号量 mutex, 并初始化
-
进入区进行P(mutex)操作以申请资源
回顾进程互斥的四个逻辑部分
-
退出区进行V(mutex)操作以释放资源
英文翻译:
mutex: 互斥体
semaphore: 信号量
使用semaphore mutex方式定义信号量一般认为是记录型信号量
typedef struct {
int value;
struct process *L;
}semaphore;
注意事项
- 使用semaphore mutex方式定义信号量一般认为是记录型信号量
- 对于不同的临界资源需要设置不同的互斥信号量
- PV操作必须成对出现
缺少P则无法保证临界资源的互斥访问
缺少V则导致资源无法被释放, 且等待进程无法被唤醒
实现进程同步
回顾
-
什么是进程同步?
- 由于进程的并发执行导致的异步的特性, 我们无法确定不同进程中指令的执行顺序 为什么需要进程同步?
- 比如进程1的指令1必须要在进程2的指令3后面执行, 这时候就需要我们提供进程同步机制来实现
如何利用PV操作来实现进程同步?
考虑P操作中资源数为0时的情况
先对value–, 然后发现value<0, 该进程阻塞
所以我们可以利用如上特性, 阻止代码的执行
实现进程同步的步骤
-
确定需要进行进程同步的地方,
即哪部分要在前面执行,
哪部分要在后面执行 -
设置同步信号量S, S.value初始化为0
-
添加PV操作
先执行的代码的末尾添加V(S)操作
后执行的代码的开头添加P(S)操作
semaphore S = 0;
P1 ( ) {
代码1;
代码2;
V(S);
代码3;
}
P2 ( ) {
P(S);
代码4;
代码5;
代码6;
}
如上代码中, 代码1,2必然在代码4,5前执行
在没有执行V(S)之前, S=0, 直接执行P(S)
会阻塞该进程
除非执行了V(S)使得S.value++后才能顺利执行P(S)以后的内容
实现进程的前驱关系
-
利用进程同步中得到的总结:
- 先执行的代码的末尾添加V(S)操作
-
后执行的代码的开头添加P(S)操作
图中:
- Sn为Pn进程中的某个指令
- x—>y表示x在y前执行
- abcdefg分别为自己创建的信号量且他们的value初始都为0
- V(a)—>P(a)表示在S1执行末尾添加V(a)操作, 在S2开始前添加P(a)操作, 其他同理
总结
使用信号量实现进程同步与进程的前驱关系时, 特别要注意总结出来的结论:
- 先执行的代码的末尾添加V(S)操作
- 后执行的代码的开头添加P(S)操作