操作系统学习笔记——进程的同步与互斥(三)
主要是理解信号量,提高做题能力,帮助形象的理解和记忆
快速链接
一、进程实现同步与互斥的几种方法
提示:为了解决临界区的互斥访问问题,操作系统给出了很多方法; 同时给出了四个原则:空闲让进,忙则等待,有限等待,让权等待。
1.软件检测法
主要是一些算法
- 单标志法:违背了空闲让进
- 双标志前检查法:违背了忙则等待
- 双标志后检查法:可能会导致“饥饿”
- Peterson’s Algorithm :增加了一个公用的标志位,等于单标结合双后的方法
2.硬件实现法
提示:利用中断和原语来实现
包括了
- 中断屏蔽法:优点是方便,缺点是降低系统效率
- 硬件指令法:利用原语,优点是可以适用于多个数目的进程,缺点是比较耗费时间
二、信号量
信号量机制其实是硬件指令法中的一种,由P&V两种原子操作来协助完成。 利用代码来介绍一下信号量的基本操作
struct {
int value;
struct proc *HEAD; //等待队列
}semaphore; //给结构体改名为信号量
void P(semaphore S){
S.value--;
if(S.value < 0){
//然后把这个进程链接到等待队列
block(); //让这个进程不再占用CPU资源
}
}
void V(semaphore S){
S.vlaue++;
if(S.value<=0){
//然后把这个进程移出来
wakeup(); //唤醒这个队列,使之进入就绪态
}
}
三、信号量_实现同步
如何体现进程同步?
先设置变量为0,要求先完成的在完成后V一下,要求后完成的在开始前P一下。
举个例子,现有两个进程分别为P1,P2,这里要求,P2进程必须要等待P1进程完全执行后才可以执行。这里面的问题就涉及到了同步问题,如果P2没有等到P1执行完就开始执行了,显然是我们不能接受的结果。
那么如何用信号量来解决这个问题呢?
- 设置同步信号量:semaphore S=0
- 区分要求逻辑上先执行完毕的进程:这里是P1
- 区分要求逻辑上后执行完毕的进程:P2
下面利用代码解决这个问题
semaphore S = 0; //设置全局同步变量
P1(){
...;
V(S); //此时的value值为0,执行完毕后,V一下,值为1
}
P2(){
p(S); //如果此时没有V过S,则值为0,再减就是-1,会阻塞
...; //相反V过之后,值为1,减1后为0,能完成资源申请
...;
}
四、信号量_实现互斥
如何体现进程间的互斥呢?
先设置变量为1,公用同一个信号量,开始用之前P,结束后V。
举个例子,现有两个进程分别为P1,P2,这里要求,P1与P2不能同时访问某一临界区,那么如何用信号量来解决这个问题呢?
- 设置同步信号量:semaphore S=1
- 然后采用复制粘贴的方式,编写两个进程的函数
下面利用代码解决这个问题
semaphore S = 1; //设置全局互斥变量
P1(){
P(S); //用之前P
//进入临界区
V(S); //结束后V
}
P2(){
P(S); //用之前P
//进入临界区
V(S); //结束后V
}
五、信号量_实现前驱关系
如何体现进程间的前驱关系?
实现前驱关系,可以视作更高级的同步。
为每个前驱要求设置独立的同步变量初始值为0。
举个例子,要求实现如下图所示的前驱关系:
理一下题目的思路,S1完成后,才可以进行S2,S3。S2完成后,才可以进行S4,S5。S3,S4,S5完成后才可以进行S6。
- 分别设置同步信号量:上面有多少条线就设置多少个同步信号量
- 然后分别书写这些进程
下面利用代码解决这个问题
semaphore S12,S13,S24,S25,S46,S56,S36 = 0; //设置全局同步变量
S1(){
...;
V(S12); //结束后V
V(S13); //结束后V
}
S2(){
p(S12); //开始前P
...;
V(S24); //结束后V
V(S25); //结束后V
}
S3(){
p(S13); //开始前P
...;
V(S36); //结束后V
}
S4(){
p(S24); //开始前P
...;
V(S56); //结束后V
}
S5(){
p(S25); //开始前P
...;
V(S56); //结束后V
}
S6(){
P(S36); //开始前P
P(S46);
P(S56);
...;
}
六、信号量的经典示例
1.消费者问题
2.读写者问题
有一个许多进程共享的数据区,这个数据区可以是一个文件或者主存 的一块空间;有一些只读取这个数据区的进程(Reader)和一些只往数据区写数据的进 程(Writer),此外还需要满足以下条件:
(1)任意多个读进程可以同时读这个文件;
(2)一次只有一个写进程可以往文件中写;
(3)如果一个写进程正在进行操作,禁止任何读进程度文件。
int count = 0; //⽤用于记录当前的读者数量量
semaphore mutex = 1; //⽤用于保护更更新count变量量时的互斥 semaphore rw = 1; //⽤用于保证读者和写者互斥地访问⽂文件
writer () { //写者进程
while (1) {
P(rw); //互斥访问共享⽂文件
Writing; //写⼊入
V(rw); //释放共享⽂文件
}
}
reader () { // 读者进程
while (1) {
P(mutex); //互斥访问count变量量
if (count == 0) { //当第⼀一个读进程读共享⽂文件时
P(rw); //阻⽌止写进程写
}
count++; //读者计数器器加1
V(mutex); //释放互斥变量量count
reading; //读取
P(mutex); //互斥访问count变量量
count--; //读者计数器器减1
if (count == 0) { //当最后⼀一个读进程读完共享⽂文件
V(rw); //允许写进程写
}
V(mutex); //释放互斥变量量count
}
}
3.哲学家问题
由Dijkstra提出并解决的哲学家进餐问题(The Dinning Philosophers Problem)是典型的同步问题。该问题是描述有五个哲学家共用一张圆桌,分别坐在周围的五张椅子上,在圆桌上有五个碗和五只筷子,他们的生活方式是交替地进行思考和进餐。平时,一个哲学家进行思考,饥饿时便试图取用其左右最靠近他的筷子,只有在他拿到两只筷子时才能进餐。进餐完毕,放下筷子继续思考。
方案一:直接拿筷子,但五个人可能会同时拿起自己左手边的筷子,是不允许的,所以只准四个人拿
semaphore chopstick[5] = {1, 1, 1, 1, 1};
semaphore r = 4;
Pi() {
while (1) {
P(r); //请求进餐
P(chopstick[i]); //请求左⼿边的筷⼦
P(chopstick[(i + 1) % 5]); //请求右⼿边的筷⼦
eat;
V(chopstick[i]); //放回左边筷⼦
V(chopstick[(i + 1) % 5]); //放回右边筷⼦
V(r);
think;
}
}
方案二:只要拿筷子,就是互斥的操作
semaphore chopstick[5]={1,1,1,1,1};
semaphore mutex = 1; //互斥地取筷子
Pi (){ //i号哲学家的进程
while(1){
P(mutex);
P(chopstick[i]); //拿左
P(chopstick[(i+1)%5]); //拿右
V(mutex);
eat;
V(chopstick[i]); //放左
V(chopstick[(i+1)%5]); //放右
think;
}
}