本来自信的以为自己能把全部的内容复习完,深夜十一点看着满桌的头发我才意识到,听着老师划的重点才是真香,虽说老师已经很明确的划了重点,但我好像还是什么都不会,脑袋里一片空白,这就是平时摸鱼的下场,摸鱼摸得爽,考试火葬场,唉,可是又有什么办法呢,只能临阵磨枪了。那么开始正文吧,加油,奥里给!
1、信号量机制
信号量实现互斥的基本原理
两个或多个进程可以通过传递信号进行合作,可以迫使进程在某个位置暂时停止执行(阻塞等待),指导它收到一个可以“向前推进”的信号(被唤醒)
相应的,将实现信号灯作用的变量成为信号量
- 信号量:仅能由同步原语进行操作的整型变量
- 同步原语:P/wait操作、V/signal操作
- 信号量类型:整形、记录型、AND型、信号量机制
一、整型信号量
1、原语描述
S:表示资源数目
int S = 1; //初始化整型信号量S,表示当前系统中可用打印机资源数
void wait(int S){ //wait源于,相当于“进入区”
while(s<=0); 。。如果资源数不够,就一直等待循环
S--; //如果资源数够,则占用一个资源
}
void signal(S){ //signal原语,相当于“退出区”
S++; //使用完资源后,在退出区释放资源
}
- 与普通整数变量的区别:对信号量的操作只有三种,即“初始化、P操作、V操作”
- “检查”和“上锁”一气呵成,避免了并发、异步导致的问题
- 不满足”让权等待“原则,会发生”忙等“
二、记录型信号量
1、原语描述
/*记录型信号量的定义*/
typedef struct {
int value; //剩余资源数
struct process *L; //等待队列
} semaphore;
/*某进程需要使用资源时,通过wait原语申请*/
void wait (semaphore S) {
S.value --;
if (S.value < 0) {
block(S,L); //如果剩余资源数不够,使用block原语使进程从运行态进度阻塞态,并把挂到信号量S的等待队列(阻塞队列)中
}
}
/*进程使用完后,使用signal原语释放*/
void signal (semaphore S) {
s.value ++;
if(S.value <= 0) {
wakeup(S,L); //释放资源后,若还有别的进程在等待这种资源,则使用wakeup原语唤醒等待队列中的一个进程,该进程从阻塞态变为就绪态
}
}
- wait(S)、signal(S)也可以记为P(S)、V(S),这对原语可用于实现对系统资源的“申请”和“释放”
- S.value 的初值表示系统中某种资源的数目
- 对信号量S的一次P操作意味着进程请求一个单位的该类资源,因此需要执行S.value–,表示资源数减1,当S.value < 0 时表示该类资源已经分配完毕,因此进程应调用block原语进行自我阻塞(当前运行的进程从运行态→阻塞态),主动放弃处理及,并且插入该类资源的等待队列S,L中。符合“让权等待”原则,不会出现”忙等“现象
- 对信号量S的一次V操作意味着进程释放一个单位的该类资源,因此需要执行S.value <= 0,表示依然有进程在等待该类资源,因此调用wakeup原语唤醒等待队列中的第一个进程(被唤醒进程从阻塞态→就绪态)
信号量的应用
信号量机制实现进程互斥
- 分析并发进程的关键活动,划定临界区(如:打印机)
- 设置互斥信号量mutex,初值为1
- 在临界区之前执行P(mutex)
- 在临界区之后执行V(mutex)
semaphore mutex = 1; //题目中无特别说明一般如此声明信号量
P1() {
…
P(mutex); //使用临界资源前要加锁
临界区代码段...
V(mutex); //使用临界资源后要解锁
…
}
P2(){
…
P(mutex);
临界区代码段...
V(mutex);
…
}
注意:
- 对不同的临界资源需要设置不同的互斥信号量(如:打印机设置为mutex1,摄像头设置为mutex2;)
- P、V操作必须成对出现
信号量机制实现进程同步
进程同步:要让各并发进程按要求有序地推进
3. 分析什么地方需要实现“同步关系”,即必须保证“一前一后”执行的两个操作(或两句代码)
4. 设置同步信号量S,初值为0;
5. 在“前操作”之后执行V(S);
6. 在“后操作”之前执行P(S);
信号量机制实现进程前驱
进程前驱:多层的同步关系
其实每一个前驱关系就是一个进程同步问题(需要保证一前一后的操作)
因此:
- 要为每一对前驱关系设置一个同步变量
- 在“前操作”之后对相应的同步变量执行V操作
- 在“后操作”之钱对相应的同步变量执行P操作
2、生产者-消费者问题
问题分析
- 系统中有一组生产者进程和一组消费者进程,生产者进程每次生产一个产品放入缓冲区,消费者进程每次从缓冲区中取出一个产品(数据)并使用
- 只有缓冲区没满时,生产者才能把产品放入缓冲区,否则必须等待
- 只有缓冲区不空时,消费者才能从中取出产品,否则必须等待
- 缓冲区是临界资源,各进程必须互斥地访问(防止并发时数据被覆盖)
如何实现
semaphore mutex = 1; //互斥信号量,实现对缓冲区的互斥访问
semaphore empty = n; //同步信号量,表示空闲缓冲区的数量
semaphore full = 0; //同步信号量,表示产品的数量,也即非空缓冲区的数量
producer () {
while(1) {
生产一个产品;
P(empty); //消耗一个空闲缓冲区
P(mutex); //*
把产品放入缓冲区;
V(mutex); //*
V(full); //增加一个产品**
}
}
*实现互斥是在同一个进程中进行一对PV操作P(mutex)
和V(mutex)
consumer (){
while(1) {
P(full); //消耗一个产品**
P(mutex);
从缓冲区中取出一个产品;
V(mutex);
V(empty); //增加一个空闲缓冲区
使用产品;
}
}
**实现两个进程同步是在其中一个进程中执行P,另一个进程中执行V操作V(full)
和P(full)
思考:能否改变相邻P、V操作的顺序
不能,实现互斥的P操作一定要在实现同步的P操作之后,即P(mutex)
一定要在P(empty)
之后,因为如果不是这样,可能会导致死锁
使用产品;
也不能放到PV之间,因为这样会降低系统的并发度:在执行使用产品;
代码时,如果不释放临界资源,其他需要临界资源的进程就无法使用,降低了系统的并发度