同步问题是一个复杂的问题,但是它也有自己的方法去处理、去分析。
PV操作系统的解题思路:
关系分析。找出题目中描述的各个进程,分析它们之间的同步、互斥关系。(从事件的角度分析)
整理思路。根据各进程的操作流程确定P、V操作的大致顺序。
设置信号量。设置需要的信号量,并根据题目条件确定信号量的初值。(互斥信号量初值一般为1,同步信号量的初始值要看对应资源的初始值是多少)
1.生产者-消费者问题
问题描述:系统中有一组生产者进程和一组消费者进程,生产者进程每次生产一个产品放入缓冲区,消费者进程每次从缓存区中取出一个产品并使用。(注:这里的"产品"理解为某种数据)
分析:
生产者、消费者共享一个初始为空、大小为n的缓冲区。
只有缓冲区没满时,生产者才能把产品放到缓冲区,否则必须等待。(缓冲区没满->生产者生产)
只有缓冲区不空时,消费者才能从中取出产品,否则必须等待。(缓冲区没空->消费者消费)
缓冲区是临界资源,各进程必须互斥的访问。

根据分析,两个同步,一个互斥,所以需要3个信号量。定义如下:
semaphore mutex=1;//互斥信号量,实现对缓冲区的互斥访问
semaphore empty=n;//同步信号量,表示空闲缓冲区的数量
semaphore full=0;//同步信号量,表示产品的数量,也即非空缓冲区的数量
//生产者
Producer(){
while(1){
生产一个产品;
P(mutex);
P(empty);
把产品放入缓冲区;
V(mutex);
V(full);
}
}
//消费者
Consumer(){
while(1){
P(mutex);
P(full);
从缓冲区中取走一个产品;
V(mutex);
V(empty);
使用产品;
}
}
注意:实现互斥的P操作一定要在实现同步的P操作之后。否则会造成死锁的现象。(V操作不会导致进程阻塞,因此两个V操作顺序可以交换)。
2.多生产者-多消费者问题
问题描述:桌子上有一个盘子,每次只能向其中放入一个水果。爸爸专向盘子中放苹果,妈妈专向盘子中放橘子,儿子装等着吃盘子中的橘子,女儿装等着吃盘子中的苹果。只有当盘子空时,爸爸或妈妈才能向盘子中放一个水果。仅当盘子中有自己需要的水果时,儿子或女儿可以从盘子中取出水果。

分析:
互斥关系:对缓冲区(盘子)的访问要互斥地进行
同步关系(一前一后):
父亲将苹果放入盘子后,女儿才能取苹果。
母亲将橘子放入盘子后,儿子才能取橘子。
只有当盘子为空时,父亲或母亲才能放入水果。

根据分析,三个同步,一个互斥,所以需要个信号量。定义如下:
semaphore mutex=1;//实现互斥访问盘子(缓冲区)
semaphore apple=0;//盘子中有几个苹果
semaphore orange=0;//盘子中有几个橘子
semaphore plate=1;//盘子中还可以放多少个水果
生产者、消费者定义如下:
Dad(){
while(1){
准备一个苹果;
P(plate);
P(mutex);
把苹果放入盘子;
V(mutex);
V(apple);
}
}
Mom(){
while(1){
准备一个橘子;
P(plate);
P(mutex);
把橘子放入盘子;
V(mutex);
V(orange);
}
Daughter(){
while(1){
P(apple);
P(mutex);
从盘子中取出苹果;
V(mutex);
V(palte);
吃掉苹果;
}
Son(){
while(1){
P(orange);
P(mutex);
从盘子中取出橘子;
V(mutex);
V(palte);
吃掉橘子;
}
注意:在本例子当中,如果不设置互斥信号量也是可以实现互斥的,原因是缓冲区大小是1,在任何时刻,三个同步信号量只有一个为1。
3.吸烟者问题
问题描述:假设一个系统有三个抽烟者进程和一个供应者进程。每个抽烟者不停地卷烟并抽掉它,但是要卷起并抽掉一支烟,抽烟者需要三种材料:烟草、纸、胶水。三个抽烟者中,第一个拥有烟草、第二个拥有纸、第三个拥有胶水。供应者进程无限地提供三种材料,供应者每次将两种材料放桌子上,拥有剩下那种材料的抽烟者卷一根烟并抽掉它,并给供应者进程一个信号告诉完成了,供应者就会放另外两种材料在桌上,这个过程一直重复(让三个抽烟者轮流地抽烟)

分析:
组合一:纸和胶水
组合二:烟草和胶水
组合三:烟草和纸
同步关系(从事件的角度分析):
桌子上有组合一->第一个抽烟者取走东西
桌子上有组合二->第二个抽烟者取走东西
桌子上有组合三->第三个抽烟者取走东西
发出完成信号->供应者将下一个组合放到桌上

和上题一样,本例子缓冲区大小为1,4个同步不信号量中同一时刻至多有一个值为1,所以不需要互斥信号量,定义如下:
semaphore offer1=0;//桌子上组合一的数量
semaphore offer2=0;//桌子上组合二的数量
semaphore offer3=0;//桌子上组合三的数量
semaphore finish=0;//抽烟是否完成
int i=0;//用于实现"三个抽烟者轮流抽烟"
供应者和抽烟者定义如下:
Provider(){
while(1){
if(i==0){
将组合一放桌上;
V(offer1);
}
else if(i==0){
将组合二放桌上;
V(offer2);
}
else if(i==0){
将组合三放桌上;
V(offer3);
}
i=(i+1)%3;
P(finish)
}
}
Smoker1(){
while(1){
P(offer1);
从桌上拿走组合一、卷烟、抽掉;
V(finish);
}
}
Smoker2(){
while(1){
P(offer2);
从桌上拿走组合二、卷烟、抽掉;
V(finish);
}
}
Smoker3(){
while(1){
P(offer3);
从桌上拿走组合三、卷烟、抽掉;
V(finish);
}
}
4.读者-学者问题
问题描述:有读者写者两组并发进程,共享一个文件,当两个或两个以上的读进程同时访问共享数据时不会产生副作用,但若某个写进程和其他进程(读进程或写进程)同时访问共享数据时则可能导致数据不一致的错误。因此要求:①允许多个读者可以同时对文件执行读操作②只允许一个写着往文件中写信息③任一写者在完成写操作之前不允许其他读者或写着工作④写者执行写操作,应让已有的读者和写者全部退出。

分析:
两类进程:写进程、读进程
互斥关系:写进程-写进程、写进程-读进程。读进程与读进程不存在互斥问题。
实现1:
semaphore rw=1; //用于实现对共享文件的互斥访问
int count =0; //记录当前有几个读进程在访问文件
semaphore mutex=1; //用于保证对count变量的互斥访问
写进程
writer() {
while(1){
P(rw); //写之前"加锁"
写文件...
V(rw); //写完了"解锁'
}
}
读进程
reader(){
while(1){
P(mutex); //各读进程要互斥访问count
if(count==0) //由第一个读进程负责
P(rw); //读之前"加锁"
count++; //访问文件的读进程数+1
V(mutex);
读文件...
P(mutex);//各读进程要互斥访问count
count--; //访问文件的读进程数+1
if(count==0) //由最后一个读进程负责
V(rw); //读完了"解锁"
V(mutex);
}
}
这个实现方法有个潜在的问题:只要有读进程还在读,写进程就要一直阻塞等待,可能"饿死"。因此,这种算法中,读进程是优先的。
还有一个问题思考一下:若两个读进程并发执行,则count=0时两个进程也许都能满足if条件,都会执行P(rw),从而使第二个读进程阻塞的情况。如何解决:出现上述问题的原因在于对count变量的检查和赋值无法一气呵成,因此可以设置另一个互斥信号量来保证各读进程对count的访问时是互斥的。这就引入了下面得到实现方法