关键词:进程同步;生产者-消费者问题;读者-写者问题;哲学家进餐问题

信号量机制可以实现互斥、同步、对一类系统资源的申请和释放。
一般来说,要实现互斥,可以设置初值为1的互斥信号量,在访问临界区之前,对这个信号量执行P操作,在访问临界区之后,对这个信号量执行V操作;
实现同步,可以设置初值为0的同步信号量(实现“一前一后”),在前操作之后,对同步信号量进行V操作,在后操作之前,对同步信号量执行P操作;
对于一类系统资源的申请和释放,可以设置一个信号量,初始值即为资源的数量(本质上也属于“同步问题”,若无空闲资源,则申请资源的进程需要等待别的进程释放资源之后才能继续往下进行)。
PV操作题目分析步骤:

  1. 关系分析。找出题目中描述的各个进程,分析它们之间的同步、互斥关系;
  2. 整理思路。根据各进程的操作流程确定P、V操作的大致顺序;
  3. 设置信号量。并根据题目条件确定信号量初值。(互斥信号量初值一般为1,同步信号量的初始值要看对应资源的初始值是多少)。

一、 生产者-消费者问题

(一) 问题描述

系统中有一组生产者进程和一组消费者进程,生产者进程每次生产一个产品放入缓冲区,消费者进程每次从缓冲区中取出一个产品并使用。生产者、消费者共享一个初始为空、大小为n的缓冲区。

(二) 问题分析

只有缓冲区没满时,生产者才能把产品放入缓冲区,否则必须等待。(同步关系)
只有缓冲区不空时,消费者才能从中取出产品,否则必须等待。(同步关系)
缓冲区是临界资源,各进程必须互斥地访问。假如系统中有两个生产者进程,当这两个生产者进程检查缓冲区为空时,一个生产者进程向缓冲区充入了一个产品(数据),在并发的环境下,另一个生产者进程也向缓冲区的同一个位置充入了它的产品(数据),这就导致了前者的数据被前者的数据所覆盖。(互斥关系)
生产者每次要消耗(P)一个空闲缓冲区,并生产(V)一个产品。消费者每次要消耗(P)一个产品,并释放一个空闲缓冲区(V),往缓冲区放入/取走产品需要互斥。刚开始空闲缓冲区的数量为n,非空闲缓冲区(产品)的数量为0。

(三) 问题实现

有产品(缓冲区没空) V------- full-------->P 消费者消费
生产者生产 P<---- empty------V 缓冲区没满

semaphore mutex = 1;//互斥信号量,实现对缓冲区的互斥访问
semaphore empty = n;//同步信号量,表示空闲缓冲区的数量
semaphore full = 0; //同步信号量,表示产品的数量,也即非空缓冲区的数量producer () {while (1) {生产一个产品;
P(empty);         //消耗一个空闲缓冲区②
P(mutex);         //实现互斥是在同一进程中进行一对PV操作①
把产品放入缓冲区;
V(mutex);
V(full);          //增加一个产品*
}
}
consumer () {while(1){P(full);          //消耗一个产品(非空闲缓冲区)*④
P(mutex);         //③
从缓冲区取出一个产品;
V(mutex);
V(empty);
使用产品;        //增加一个空闲缓冲区
}
}

*实现两进程的同步关系,是在其中一个进程中执行P,另一进程中执行V。

(四) 问题延伸

  1. 若将操作①②位置调换,即互斥的mutex 的P操作在前,同步的empty的P操作在后,会发生什么情况?
    如果此时缓冲区内已经放满产品,意味着空闲缓冲区empty=0,非空闲缓冲区(产品数量)full=n。则生产者进程执行① 使mutex变为0,再执行②,由于已没有空闲缓冲区,因此生产者被阻塞。
    由于生产者阻塞,因此切换回消费者进程。消费者进程执行③,由于mutex为0,即生产者还没释放对临界资源的“锁”,因此消费者也被阻塞。这就造成了生产者等待消费者释放空闲缓冲区,而消费者又等待生产者释放临界区的访问权限,生产者和消费者循环等待被对方唤醒,因此出现了“死锁”现象。
    同样的,若缓冲区中没有产品,即full=0,empty=n。按③④① 的顺序执行就会发生死锁。
    因此,实现互斥的P操作一定要在实现同步的P操作之后。V操作不会导致进程阻塞,因此两个V操作顺序可以交换。
  2. 能否将生产者进程的生产一个产品操作和消费者进程的使用产品操作放到PV操作之间呢?
    逻辑上来看是没有问题的,可以让消费者从缓冲区取出一个产品之后,就紧接着使用产品,但这会导致临界区的代码量变大,那么消费者进程在访问临界区就需要耗费更长的时间,如果此时有别的进程也想访问临界区的话,就会被阻塞。如果把非必要的代码也放入临界区的话,就会导致程序之间的并发度降低。

二、 读者-写者问题

(一) 问题描述

有读者和写者两组并发进程,共享一个文件,当两个或两个以上的读进程同时访问共享数据时不会产生副作用,但若某个写进程和其他进程(读进程或写进程)同时访问共享数据时则可能导致数据不一致的错误。因此要求:①允许多个读者可以同时对文件执行读操作;②只允许一个写者往文件中写信息;③任一写者在完成写操作之前不允许其他读者或写者工作;④写者执行写操作前,应让已有的读者和写者全部退出。

(二) 问题分析

与消费者进程不同,读者进程在读数据后并不会将数据清空,并不会改变数据。因此多个读者可同时访问共享数据。读进程与写进程同时共享数据,可能导致读出的数据不一致的问题。两个写进程同时共享数据,可能导致数据错误覆盖的问题
两类进程:写进程、读进程
互斥关系:写进程—写进程、写进程—读进程。读进程与读进程不存在互斥问题。
写者竞争和任何进程都互斥,可以设置一个互斥信号量rw,用来表示此时是否有别的进程正在访问共享文件。在写进程对共享文件进行访问之前,可以先执行一个P操作,当它对共享文件访问结束之后,再进行一个V操作,来释放对共享文件加的锁。
读者进程和写字进程也要互斥,因此在读者访问共享文件之前,也需要对互斥信号量rw执行P操作进行加锁,读完文件之后进行V操作解锁。
虽然读者和写者之间是互斥的,但是读者和读者之间可以同时访问这个共享。如果说按照以上方法,第一个读者进程在尝试读文件之前P操作,可以顺利地通过开始读文件,第二个进程如果也要执行P操作的话,第二个读进程就会被阻塞,所以就没办法实现两个读进程同时访问共享文件的事情。
从逻辑上来看,对rw这个互斥信号量的PV操作其实可以理解为对文件的加锁和解锁。那么可以设置一个叫做count的整形变量用来记录当前有几个读进程正在访问这个文件。如果此时是第一个读进程想要尝试访问这个文件,就应该对文件进行加锁;而如果是第二个读进程想要尝试访问这个文件的话,那么在经过判断之后就不应该让第二个读进程再进行一次加锁的操作,应该让第二个读进程进程直接跳过这个P操作,就可以直接开始访问这个文件。

(三) 问题实现

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 变量的检查和赋值无法一气呵成,因此可以设置另一个互斥信号量mutex来保证各读进程对count 的访问是互斥的。
而以上的算法存在一个潜在的问题,只要有读进程还在读,写进程就要一直阻塞等待,可能“饿死”。因此,这种算法中,读进程是优先的。针对这个情况,进行以下处理:

semaphore rw=1; //用于实现对共享文件的互斥访问
int count = 0; //记录当前有几个读进程在访问文件
semaphore 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--;
if(count==0)
V(rw);
V(mutex);
}
}

分析以下并发执行 P(w) 的情况:
读者—>读者2: 第一个读者进程可以顺利的通过对w的PV操作进行读文件,读者进程已经对w和mutex这两个互斥信号量执行了V操作,所以如果第二个读进程到达的话,也可以顺利地读文件。
写者—>写者2: 两个写者并发执行的话,那么第一个写者可以顺利的通过w和rw的两个P操作开始写文件,第二个写者在执行到w的P操作的时候就会被阻塞,一直到第一个写者写完文件并且对w这个互斥信号量进行V操作之后第二个写者才可以跳过阻塞开始写文件。
写者—>读者1: 一个写者进程顺利的执行了w和rw的两个P操作开始写文件,而读者进程在执行到w的P操作的时候就会被阻塞,一直要等待写者进程写完文件并且对w互斥信号量进行了V操作之后,读者进程才可以跳过阻塞开始往下执行读文件。
读者—>写者—>读者2: 如果一个读者正在读这个文件的话,那么他肯定是顺利的执行了读文件上面的那些操作,包括对rw的P操作。如果在读文件的过程中有新的写者进程到达,由于第一个读者进程已经对w这个信号量执行过了V操作,所以这个写者进程执行这个P操作的时候不会被阻塞,在执行对rw的P操作的时候,由于第一个读者进程已经对rw的执行了一个P操作也就是加锁,所以写作进程会被阻塞在rw的位置。那此时如果有第二个读者进程到达的话,由于之前读者进程已经对w执行了P操作,但是并没有执行V操作,所以这个读者进程在执行这个P操作的时候就会被阻塞在这个地方,一直到第一个读进程读完文件之后,它要进行了count–,发现count的值变为了0。于是会对rw这个互斥信号量执行V操作。所以写进程本来是阻塞在rw的P操作的位置,当读进程执行了rw的V操作之后,写者进程就可以被唤醒,开始写文件。而第二个读进程依然是被阻塞在w的P操作的位置。所以这就避免了上一个算法的“读优先”写进程会一直饥饿的问题。
写者—>读者—>写者2: 如果说是一个写进程先执行,那么这个写者进程已经顺利地执行了w和rw的P操作。如果此时到达了一个读者进程,那么这个读进程首先会被阻塞在w的P操作的位置,此时如果第二个写者进程到达的话,也会被阻塞在w的P操作的位置。也就是需要等待w这个互斥信号量被释放,但是由于读者进程是先到达的,所以当第一个写者进程执行完写操作并对w执行完V操作,那么这个V操作先唤醒的应该是先对w执行P操作的进程,也就是读者进程。
结论:在这种算法中,连续进入的多个读者可以同时读文件;写者和其他进程不能同时访问文件;写者不会饥饿,但也并不是真正的“写优先”,而是相对公平的先来先服务原则。

三、 哲学家进餐问题

(一) 问题描述

一张圆桌上坐着5名哲学家,每两个哲学家之间的桌上摆一根筷子,桌子的中间是一碗米饭。哲学家们倾注毕生的精力用于思考和进餐,哲学家在思考时,并不影响他人。只有当哲学家饥饿时,才试图拿起左、右两根筷子(一根一根地拿起)。如果筷子已在他人手上,则需等待。饥饿的哲学家只有同时拿起两根筷子才可以开始进餐,当进餐完毕后,放下筷子继续思考。

(二) 问题分析

  1. 关系分析。系统中有5个哲学家进程,5位哲学家与左右邻居对其中间筷子的访问是互斥关系;
  2. 整理思路。这个问题中只有互斥关系,但与之前遇到的问题不同的事,每个哲学家进程需要同时持有两个临界资源才能开始吃饭。如何避免临界资源分配不当造成的死锁现象,是哲学家问题的精髓;
  3. 信号量设置。定义互斥信号量数组chopstick[5]={1,1,1,1,1} 用于实现对5个筷子的互斥访问。并对哲学家按0~4编号,哲学家 i 左边的筷子编号为 i,右边的筷子编号为 (i+1)%5。
    如果每个哲学家吃饭前依次拿起左、右两支筷子,而恰好5个哲学家并发地拿起了自己左手边的筷子,则每位哲学家循环等待右边的人放下筷子(阻塞)。发生“死锁”。
    如何防止死锁的发生呢?
  4. ①可以对哲学家进程施加一些限制条件,比如最多允许四个哲学家同时进餐。这样可以保证至少有一个哲学家是可以拿到左右两只筷子的;
  5. ②要求奇数号哲学家先拿左边的筷子,然后再拿右边的筷子,而偶数号哲学家刚好相反。用这种方法可以保证如果相邻的两个奇偶号哲学家都想吃饭,那么只会有其中一个可以拿起第一只筷子,另一个会直接阻塞。这就避免了占有一支后再等待另一只的情况。
  6. ③仅当一个哲学家左右两支筷子都可用时才允许他抓起筷子。

(三) 问题实现

1.   仅限4人同时进餐
semaphore chopstick[5] = {1,1,1,1,1};
semaphore limit = 4;                  //互斥地取筷子
Pi (){                                //i号哲学家的进程
while(1){P(limit);
P(chopstick[i]);                 //拿左
P(chopstick[(i+1)%5]);           //拿右
V(limit);
吃饭…
V(chopstick[i]);                 //放左
V(chopstick[(i+1)%5]);           //放右
思考…
}
}
2.  奇数号哲学家拿左右筷子,偶数号哲学家拿右左筷子
semaphore chopstick[5] = {1,1,1,1,1};
Pi (){                                //i号哲学家的进程
while(1){                          //偶数号哲学家
if(i/2 ==0){P(chopstick[(i+1)%5]);            //拿右P(chopstick[i]);                  //拿左吃饭……V(chopstick[i]);V(chopstick[(i+1)%5]);思考……
}
else{                              //奇数号哲学家P(chopstick[i]);                  //拿左P(chopstick[(i+1)%5]);            //拿右吃饭……V(chopstick[i]);V(chopstick[(i+1)%5]);思考……
}}
3.  同时拿两支筷子
记录型信号量
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);
吃饭…
V(chopstick[i]);                 //放左
V(chopstick[(i+1)%5]);           //放右
思考…
}
}
AND型信号量
semaphore chopstick[5] = {1,1,1,1,1};
semaphore mutex = 1;                  //互斥地取筷子
Pi (){                                //i号哲学家的进程
while(1){Swait(chopstick[i], chopstick[(i+1)%5]);
吃饭…
Ssignal(chopstick[i], chopstick[(i+1)%5]);
思考…
}
}

参考文献

[1]汤小丹:《计算机操作系统(第4版)》,西安:西安电子科技出版社2014年版。
[2]王道论坛:《操作系统考研复习指导》,北京:电子工业出版社2019年版。
[3]梁红兵:《计算机操作系统(第4版)》学习指导与题解,西安:西安电子科技出版社2015年版。

本文文本源自哔哩哔哩网站王道论坛的视频“2019 王道考研 操作系统 P22、P25、P26”

经典进程同步问题总结相关推荐

  1. 计算机操作系统经典进程同步问题

    经典进程同步问题 1 )生产者-消费者问题(合作互斥) 1)利用记录型信号量解决 2)利用AND型信号量解决 2)哲学家进餐问题(资源耗尽,避免死锁) 1)利用记录型信号量解决 2)利用AND型信号量 ...

  2. 经典进程同步与互斥问题

    经典进程同步与互斥问题 1. 生产者-消费者问题 1.1 简单的"生产者-消费者"问题 设进程A.B是两个相互合作的进程,它们共享一个缓冲区,进程A向其中写入数据,进程B从中读出数 ...

  3. 操作系统:经典进程同步问题 之 生产者-消费者问题、读者-写者问题、哲学家进餐问题

    在进程同步中,经典的同步问题有:生产者-消费者问题.读者-写者问题.哲学家进餐问题. 一.生产者与消费者问题: 问题描述:使用一个缓冲区来保存物品,只有缓冲区没有满,生产者才可以放入物品:只有缓冲区不 ...

  4. 经典进程同步问题(一)——生产者消费者问题

    目录 一.生产者消费者问题描述 二.解决思路 三.问题求解: 四.源码 五.运行结果: 一.生产者消费者问题描述 生产者消费者(producer-customer)问题是一个非常著名的进程同步问题.它 ...

  5. 经典进程同步问题——生产者消费者问题

    问题描述 "生产者-消费者"问题 (producer/consumer problem) 是最著名的进程同步问题. 该问题描述了共享固定大小缓冲区的两个线程--即所谓的" ...

  6. 经典进程同步问题(十)

    生产者–消费者问题          读者–写者问题          哲学家进餐问题   哲学家就餐问题可以这样表述,假设有五位哲学家围坐在一张圆形餐桌旁,做以下两件事情之一:吃饭,或者思考.吃东西 ...

  7. 二、进程管理(4.经典进程同步问题)

    1. 生产者-消费者问题 利用记录型信号量解决生产者-消费者问题 这类问题就是两个进程对一个临界资源的使用的具体例子. 假定在生产者和消费者之间的共用缓冲池(就像是仓库),共有n个缓冲区(n个位置). ...

  8. 进程同步生产者-消费者问题C语言,经典进程同步问题 --- 生产者和消费者

    问题描述: 一组生产者进程和一组消费者进程共享一个初始为空.大小为 n 的缓冲区,只 有缓冲区没满时,生产者才能把消息放入到缓冲区,否则必须等待;只有缓冲区 不空时,消费者才能从中取出消息,否则必须等 ...

  9. 经典进程同步问题——吸烟者问题

    吸烟者问题是为了解决"可以生产多个产品的单生产者"问题提供了一个思路. 问题描述:有三个抽烟者和一个供应者.每个抽烟者不停地卷烟抽,组成一根烟需要三种材料:烟草.纸和胶水.三个抽烟 ...

最新文章

  1. SQL Server 默认跟踪(Default Trace)
  2. 【iOS】【项目全局动态埋点】Runtime+Aspects(hook)
  3. angular 自定义指令参数详解
  4. CSDN社区之星专訪:我的蜕变之路
  5. 问题来了,个人用户可以使用短信接口吗?答案很意外
  6. ubuntu 20.04安装谷歌拼音输入法
  7. 手机连接wifi自动弹窗的原理及其实现方法
  8. JAVA知识体系之分布式篇(四)——Kafka
  9. 2019年天梯赛第一阶段(1-8)全解
  10. 携程Java后台开发面经
  11. OSG使用OpenGL(以及glad库)绘制自定义图形
  12. win 32学习笔记(三) 消息队列
  13. 《信息物理融合系统(CPS)设计、建模与仿真——基于 Ptolemy II 平台》——1.3 模型在设计中的作用...
  14. Cloudsim和算法
  15. mysql usleep_PHP 暂停函数 sleep() 与 usleep() 的区别
  16. 游客丽江住店被蚊子咬醒 前台:养的宠物 死1只赔100
  17. 【bug】data functions should return an object
  18. 结构体【struct】
  19. clahe(限制对比度自适应直方图均衡化)
  20. Appium+java+Android 四(appium实现自动化发朋友圈用例)

热门文章

  1. 机顶盒开发前奏---电脑adb无线连接机顶盒
  2. XXX.EXE已停止工作
  3. 面试官:谈谈你对双亲委派模型的理解?
  4. 深度解析RARP协议
  5. 力扣35. 搜索插入位置python实现
  6. 开箱即用!Linux 内核首个原生支持,让你的容器体验飞起来!| 龙蜥技术
  7. 文本安装红旗Linux,红旗Linux桌面4.1正式版文本方式安装过程详细图解.doc
  8. 办公软件python_《办》字意思读音、组词解释及笔画数 - 新华字典 - 911查询
  9. LeetCode题解(0827):最大人工岛(Python)
  10. Xceed Ultimate Suite 22.3 Crack