信号量机制
用户进程可以通过使用操作系统提供的一对原语来对信号量进行操作,从而很方便的实现了进程互斥、进程同步
信号量
定义 : 信号量其实就是一个变量(可以是一个整数),用来表示系统中某种资源的数量。
比如系统中只有一台打印机,就可以设置一个初值为 1 的信号量。
对信号量的操作只有三种 : 初始化 、 P操作(wait操作) 、 V操作(signal操作)
记录型信号量
定义 : 整形限号量不满足让权等待的条件,容易出现忙等的现象,因此人们提出了“记录型信号量” , 即用记录型数据结构表示的信号量
/*记录型信号量的定义*/
typedef struct{
int value;
struct process *L; // 等待队列
}semaphore
/*某进程需要使用资源的时候,通过 wait 原语申请*/
void wait(semaphore S){
S.value -- ; // 先减下来
if(S.value < 0) block(S.L); //如果已经不够了,加入等待队列
}
/*进程使用完资源之后,通过signal原语释放*/
void signal(semaphore S){
S,value ++; // 释放了就加上去
if(S.value <= 0) wakeup(S.L);//如果这个时候有人在等待的话,就叫起来一个进程
}
其实PV操作很像的,都是对信号量的一个处理,都是信号量与进程之间的联系,对于P操作(wait)操作来说,就是根据信号量的多少来确定,进程是否可以获取资源;对于V操作来说就是在结束的时候释放资源,以便其他进程可以进入。
信号量总结
![](https://img2022.cnblogs.com/blog/2772093/202208/2772093-20220807124258492-1150175073.jpg)
原语
定义 : 原语是一种特殊的程序段,它的执行不可以被打断。是由关中断\开中断指令实现的。
这样做的意义是,不用害怕在执行过程中被人抢了去
P原语举例
P原语即 wait 原语 相当于“进入区” 在这里程序会检验系统中资源的数量(信号量) 如果够的话就进入程序 如果不够的话会循环等待
void wait(int S){ // 只是单纯的整形变量 不满足让权等待的条件,容易出现忙等的现象
while(S <= 0); //如果资源不够的话会循环等待,进程停止在这个地方
S = S - 1;
}
V原语举例
V原语即 signal 原语 相当于“退出区” 使用完之后,会在退出区释放该资源
void signal(int S){
S = S + 1;
}
![](https://img2022.cnblogs.com/blog/2772093/202208/2772093-20220807124624056-222555544.jpg)
信号量机制实现进程互斥
-
分析并发进程的关键活动,划定临界区(如: 对临界资源打印机的访问就应该放在临界区)
-
将临界区理解为一种特殊的系统资源,信号量初始值为 mutex = 1;
-
在临界区之前执行P(mutex)
-
在临界区之后执行V(mutex)
注意 : 对不不同的临界区资源要使用不同的信号量进行互斥
信号量机制实现进程同步
用信号量实现进程同步
-
分析在什么地方需要实现“同步关系”,即必须保证“一前一后”执行的两个操作(或者两句代码)
-
设置同步信号量 初始值为 0
-
在“前操作”之后执行V(S)
-
在“后操作”之前执行P(S)
/*信号量机制实现同步*/
semaphore S = 0; // 定义同步信号量初始值为 0
P1(){
code 1;
code 2;
V(S); // S ++;
code 3;
}
P2(){
P(S); //S --;
code 4;
code 5;
code 6;
}
观察一下和我们之前见到的PV操作有什么不同 ? 信号量初始值为 0 对不对。这样其实就导致了我们在遇到P操作的时候是没办法直接进入的,进程会在P操作里面主动请求进入等待队列,进行等待;直到另一个进程执行完了V操作,将我唤醒。所以你若想先执行 A 再执行 B ,就在 B 的前面加上一个信号量初始值为 0 P操作,在 A 后面加上一个对应的 V 操作,这样就可以保证先执行 A 再执行 B了。
方便记忆小技巧 : 前下V 后上P
信号量机制实现前驱关系
和进程同步很像,是一种更复杂的进程同步问题,无非就是多涉及了几个信号量
![](https://img2022.cnblogs.com/blog/2772093/202208/2772093-20220807124351624-1110529745.jpg)
![](https://img2022.cnblogs.com/blog/2772093/202208/2772093-20220807124410090-1073492269.jpg)
进程之间的前驱关系,每一个节点都是一个进程,每一个进程执行之前要先看看自己需不需要P一下获取资源,在执行完之后需要V一下对其他进程负责
生产者与消费者问题
生产者消费者共享一个初始为空、大小为n的缓冲区。
需要注意的是 :
-
只有缓冲区没有满的时候,生产者才能将产品放入缓冲区,否则必须等待。
-
只有缓冲区不是空的时候,消费者才能将产品从缓冲区中取出,否则必须等待。
上面两种操作需要进行同步操作
- 缓冲区是临界资源,各进程必须互斥的访问。
需要进行互斥操作
![](https://img2022.cnblogs.com/blog/2772093/202208/2772093-20220807124431576-730006523.jpg)
多生产者与多消费者问题
![](https://img2022.cnblogs.com/blog/2772093/202208/2772093-20220807124445944-1183947538.jpg)
![](https://img2022.cnblogs.com/blog/2772093/202208/2772093-20220807124458005-1763566457.jpg)
吸烟者问题
![](https://img2022.cnblogs.com/blog/2772093/202208/2772093-20220807124514042-224626627.jpg)
![](https://img2022.cnblogs.com/blog/2772093/202208/2772093-20220807124525694-1027240395.jpg)
![](https://img2022.cnblogs.com/blog/2772093/202208/2772093-20220807124536112-856756341.jpg)
读者写者问题
要求 : 多个读者的操作可以同时执行,多个写者的操作不可以同时执行,读者与写者的任何操作不可以同时进行 , 写者不可以等待太长时间(饥饿)
解答:
-
如何实现多个读者之间可以一起进行数据读取嘞 ? 使用一个互斥信号量,进来的第一个读者上 P 出去的最后一个读者放 V
-
如何实现读者与写着不可以同步进行嘞? 他俩使用一个互斥信号量即可
互斥信号量初值设为 1 ,这样所有使用临界区的进程都被限制在这个信号量之下,进而实现互斥。
/*
semaphore rw = 1; // 用于实现对文件的互斥访问
int count = 0 ;// 记录当前有几个读进程在访问文件
seamphore mutex = 1;//用于保证对 count 变量的互斥访问
semaphore w = 1;//用于实现"写优化"
*/
writer(){
while(1){
P(W);
P(rw);
写文件...
V(rw);
V(W)
}
}
reader(){
while(1){
P(w);//如果写进程要开始写了,读进程就不可以再进入新的了
P(mutex);
if(count = 0) P(rw); // 如果是第一个进来的读进程,那么我们要上一个互斥变量
count ++;
V(mutex);
V(w);//注意这里限制的只是不再进入新的读进程,已经进入的读进程需要继续进行
读文件...
P(mutex);//又要对count进行操作了
count --;
if(count == 0) V(rw);//最后一个出去的读进程释放临界区
V(mutex);
}
}