进程管理(二[2])
Posted 生命是有光的
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了进程管理(二[2])相关的知识,希望对你有一定的参考价值。
【王道】操作系统OS第二章进程管理
1、信号量机制
-
用户进程可以通过使用操作系统提供的一对原语来对信号量进行操作,从而很方便的实现了进程互斥、进程同步。
-
信号量其实就是一个变量(可以是一个整数,也可以是更复杂的记录型变量),可以用一个信号量来表示系统中某种资源的数量,比如:系统中只有一台打印机,就可以设置一个初值为1的信号量。
-
原语是一种特殊的程序段,其执行只能一气呵成,不可被中断。原语是由关中断/开中断指令实现的。软件解决方案的主要问题是进入区的各种操作无法一气呵成,因此如果能把进入区、退出区的操作都用原语实现,使这些操作能一气呵成就能避免问题。
-
一对原语:指的是
wait(S)
原语和signal(S)
原语,可以把原语理解为我们自己写的函数,函数名分别为wait和signal,括号里的信号量S其实就是函数调用时传入的一个参数。 -
wait、signal 原语常简称为P、V操作(来自荷兰语proberen 和verhogen)。因此,做题的时候常把wait(S)、signal(S) 两个操作分别写为P(S)、V(S)
1.1、整型信号量
用一个整数型的变量作为信号量,用来表示系统中某种资源的数量 (与普通整数变量的区别:普通的整数变量可以进行加减乘除等运行,而信号量的操作只有三种,即初始化、P操作、V操作)
int S = 1; //初始化整型信号量S,表示当前系统中可用的打印机资源数
void wait(int S) // wait原语,相当于"进入区"
while(S≤0); // 如果资源数不够,就一直循环等待
S=S-1; // 如果资源数够,则占用一个资源
void signal(int S) // signal 原语,相当于"退出区"
S=S+1; // 使用完资源后,在退出区释放资源
// 进程p0
wait(S); //进入区,申请资源
使用打印机资源 //临界区,访问资源
signal(S); //退出区,释放资源
如果有一个进程P0要使用打印机资源,由于这种资源是有限的只有一个,并且我们需要互斥的访问打印机,所以在使用打印机资源之前,进程P0必须要使用一个wait原语对信号量S进行操作,wait原语会做两件事情,
-
第一个是判断当前资源数是否足够,如果资源数S≤0,就说明系统当中已经没有这种资源了,那么P0进程就会被一直卡住。但是上述代码中由于P0进程执行wait原语的时候S的值是1,所以不会被卡住,从而执行第二个事 S=S-1=0,也就是说这个打印机资源已经被进程P0使用了,没有其他打印机资源了。
-
当P0在访问打印机资源当中,如果发生了进程切换,有另外的进程P1也想使用打印机资源,那么P1在使用之前先执行 wait 原语,不过由于此时S的值已经是0,也就是说系统当中已经没有打印机资源了,所以P1会在执行while循环时卡住。直到P0进程释放打印机资源,执行signal原语使得S值+1,P1才能不被卡住。其他进程若想使用打印机资源与P1相同。
- 因为是用原语来实现的检查和上锁,所以就避免了两个进程同时进入临界区的问题。
整型信号量存在的问题:当进程被卡在while循环时,进程如果获取不到资源,会导致进程一直占用处理机,产生"忙等"的情况,并不满足"让权等待"的原则。
1.2、记录型信号量
整型信号量的缺陷是存在"忙等"问题,因此人们又提出了"记录型信号量",即用记录型数据结构表示的信号量。
//记录型信号量的定义
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原语唤醒等待队列中的一个进程,该进程从阻塞态变为就绪态
举个例子:某计算机系统中有2台打印机,则可在初始化信号量S时将S.value的值设为2,队列S.L设置为空。各个进程在使用打印机资源之前需要使用wait原语来申请打印机资源,在使用打印机资源之后需要使用signal原语来释放打印机资源。
-
首先p0进程要使用打印机资源,CPU为p0进程服务,使用wait原语来申请打印机资源,在wait原语中,
S.value--
,此时S.value的值变为1,代表剩余打印机数量为1 -
之后p1进程要使用打印机资源,CPU为p1进程服务,使用wait原语来申请打印机资源,在wait原语中,
S.value--
,此时S.value的值变为0,代表剩余打印机数量为0 -
之后p2进程要使用打印机资源,CPU为p2进程服务,使用wait原语来申请打印机资源,在wait原语中,
S.value--
,此时S.value的值变为-1,由于S.value<0
,所以会使用block原语进行自我阻塞(p2进程从运行态->阻塞态),S.value=-1,表示有1个进程在等待 -
之后p3进程要使用打印机资源,CPU为p3进程服务,使用wait原语来申请打印机资源,在wait原语中,
S.value--
,此时S.value的值变为-2,由于S.value<0
,所以会使用block原语进行自我阻塞(p3进程从运行态->阻塞态),S.value=-2,表示有2个进程在等待 -
p0使用完打印机资源之后会执行 signal 原语,在signal原语中,
S.value++
,此时S.value的值变为-1,由于S.value≤0
,所以会使用wakeup原语唤醒等待队列中的第一个进程(被唤醒进程从阻塞态->就绪态),也就是p2进程被唤醒,CPU为p2进程服务,并且将p0进程释放的打印机资源给p2进程使用
-
在考研题目中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++,表示资源数加1,若加1后仍是S.value<=0,表示依然有进程在等待该类资源,因此应调用wakeup原语唤醒等待队列中的第一个进程(被唤醒进程从阻塞态->就绪态)
1.3、小结
2、信号量机制的实现
- 一个信号量对应一种资源
- 信号量的值 = 这种资源的剩余数量(信号量的值如果小于0,说明此时有进程在等待这种资源)
- P(S)-申请一个资源S,如果资源不够就阻塞等待
- V(S)-释放一个资源S,如果有进程在等待该资源,则唤醒一个进程
2.1、信号量机制实现进程互斥
系统当中的某些资源,是必须互斥访问的,而访问这种资源的代码叫做临界区。
- 分析并发进程的关键活动,划定临界区(如:对临界资源打印机的访问就应放在临界区)
- 设置互斥信号量mutex,初值为1
- 在进入区P(mutex)–申请资源
- 在退出区V(mutex)–释放资源
//记录型信号量的定义
typedef struct
int value; //剩余资源数
struct process *L; //等待队列
semaphore;
我们自己要会定义记录型信号量,但是如果题目中没有特别说明,则我们可以把信号量的声明简单写成下 semaphore mutex
// 信号量机制实现互斥
semaphore mutex=1 //初始化信号量(默认是记录型信号量)
P1()
P(mutex); //使用临界资源前需要加锁
临界区代码段...
V(mutex); //使用临界资源后需要解锁
P2()
P(mutex); //使用临界资源前需要加锁
临界区代码段...
V(mutex); //使用临界资源后需要解锁
- 当一个进程进入临界区之前,需要执行 P(mutex) ,出了临界区之后需要执行 V(mutex),这样就可以实现进程对临界区代码的互斥访问。
- 我们可以认为信号量 mutex 表示的是 进入临界区的名额,初值为1,表示刚开始进入临界区的名额只有1个,当进程P1执行 P(mutex) 其实就是申请一个进入临界区的名额,如果这种名额还有余量,则进程P1可以顺利进入临界区。如果此时进程P2执行 P(mutex) 申请名额,因为名额为0,所以会阻塞等待。直到进程P1执行 V(mutex) 归还名额,这个时候P2进程会被唤醒进入临界区。
注意:
-
对不同的临界资源需要设置不同的互斥信号量。例如临界区资源为打印机和摄像头,则必须为打印机和摄像头分别设置互斥信号量mutex1和mutex2
-
P、V操作必须成对出现。缺少P(mutex)就不能保证临界资源的互斥访问。缺少V(mutex)会导致资源永不被释放,导致等待的进程永不被唤醒。
2.2、信号量机制实现进程同步
进程同步:要让各并发进程按照要求的顺序有序地推进。
怎么用信号量机制实现进程同步:
- 分析什么地方需要实现"同步关系",即必须保证"**一前一后"**执行的两个操作(某一句代码必须要在前,某一句代码必须要在后)
- 设置同步信号量S,初始为0
- 在"前操作"之后执行V(S)
- 在"后操作"之前执行P(S)
如下图,代码2必须在前执行,代码4必须在后执行,所以代码2是"前操作",代码4是"后操作",所以在前操作代码2之后执行V操作,在后操作代码4之前执行P操作。
技巧口诀:前V后P
2.3、信号量机制实现前驱关系
进程P1中有句代码S1,P2中有句代码S2…,P3中有句代码S6。这些代码要求按如下前驱图所示的顺序来执行。
这个前驱图的意思是,只有S1这一句代码执行了之后,才可以执行S2这一句代码,而只有S2执行了代码之后,才可以执行S4和S5这两句代码。只有S4、S5、S3执行之后,才可以执行S6。
其实每一对前驱关系都是一个进程同步问题(需要保证一前一后的操作)。因此:
- 要为每一对前驱关系各设置一个同步信号量(设置a、b、c、d、e、f、g为同步信号量,并且同步信号量的初值都为0,也就是说这种资源刚开始是没有的,这种资源只能由前面的进程的V操作产生)
- 在"前操作"之后对相应的同步信号量设置V操作(S1在前,S2在后,所以S1为前操作,S2为后操作,在S1之后执行V操作,在S2之前执行P操作)
- 在"后操作"之前对相应的同步信号量变量执行P操作(S1在前,S2在后,所以S1为前操作,S2为后操作,在S1之后执行V操作,在S2之前执行P操作)
前V后P
S1要在S2之前执行,所以S1之后要执行V操作,S2要在之前执行P操作
S1要在S3之前执行,所以S1之后要执行V操作,S3要在之前执行P操作
上述各个进程以不同的顺序上处理机运行,可以实现同步。例如P5上处理机,执行P(d),而由于d初值为0,所以P5进程会阻塞在这里,接下来发生进程调度,假设切换为P2上处理机,执行P(a),而由于a初值为0,所以P2进程会阻塞在这里,接下来发生进程调度,假设切换为P1上处理机,执行S1、V(a)、V(b),V(a)会唤醒P2进程,所以接下来P2进程才会执行S2代码,也就是说S2肯定是在S1之后执行的,这样就实现了进程同步。
2.4、小结
3、进程同步互斥问题
3.1、生产者消费者问题
系统中有一组生产者进程和一组消费者进程,生产者进程每次生产一个产品放入缓冲区,消费者进程每次从缓冲区取出一个产品并使用。(注:这里的"产品"理解为某种数据),生产者、消费者共享一个初始为空、大小为n的缓冲区。
- 只有缓冲区没满时,生产者才能把产品放入缓冲区,否则必须阻塞等待。(缓冲区没满->生产者生产,这是一前一后)
- 只有缓冲区不空时,消费者才能从中取出产品,否则必须阻塞等待(缓冲区没空->消费者消费,这是一前一后)
- 缓冲区是临界资源,各进程必须互斥的访问(互斥关系)
- 若进程并发的运行,两个生产者进程并发的写入缓冲区,缓冲区又是有限的,难免出现数据覆盖,所以缓冲区才是临界资源,各个进程才要互斥的访问。
- 第一个关系是只有当缓冲区里有产品的时候,消费者进程才可以消费;第二个关系是只有缓冲区没满的时候,生产者进程才可以生产。所以这两个一前一后的关系我们就需要给他们设置同步信号量full、empty,并且在前面的操作完成之后对同步信号量执行V操作,在后面的操作完成之前对同步信号量执行P操作。
- 除了上述两对一前一后的关系外,还要实现对临界区的互斥访问,所以还需要设置互斥信号量mutex,让其初值为1,在临界区的前面和后面分别执行P、V操作即可。
PV操作题目分析步骤:
- 关系分析。找出题目中描述的各个进程,分析它们之间的同步、互斥关系
- 缓冲区没满->生产者生产 => 一前一后同步关系
- 缓冲区没空->消费者消费 => 一前一后同步关系
- 缓冲区是临界区 => 互斥访问互斥关系
- 整理思路。根据各进程的操作流程确定P、V操作的大致顺序。
- 设置信号量。并根据题目条件确定信号量初值。(互斥信号量初值一般为1,同步信号量的初始值要看对应资源的初始值是多少)
-
消费者进程在消费之前,需要消耗产品资源,所以在它之前的P操作其实是在申请一个产品,因此full同步信号量它所对应的资源是产品的数量,也就是非空缓冲区的数量,而题中刚开始产品的数量是0,所以full初始值为0
-
生产者每生产一个产品就需要消耗一个空闲缓冲区,因此 empty 这个同步信号量它所对应的资源就应该是空闲缓冲区这种资源,它的数量就是空闲缓冲区的数量,题目中的空闲缓冲区的数量为n,所以empty初始值为n
semaphore mutex = 1; // 互斥信号量,实现对缓冲区的互斥访问
semaphore empty = n; // 同步信号量,表示空闲缓冲区的数量
semaphore full = 0; // 同步信号量,表示产品的数量,也即非空缓冲区的数量
// 生产者
producer()
while(1)
生产一个产品;
P(empty); //消耗一个空闲缓冲区
P(mutex);
把产品放入缓冲区
V(mutex);
V(full); //增加一个产品
// 消费者
consumer()
while(1)
P(full); //消耗一个产品(非空缓冲区)
P(mutex);
从缓冲区取出一个产品;
V(mutex);
V(empty); //增加一个空闲缓冲区
使用产品;
生产者要做的就是不断的生产一个产品,然后把产品放入缓冲区。消费者要做的就是不断的从缓冲区取出一个产品并使用。生产者在把产品放入缓冲区之前,需要申请一个空闲的缓冲区,所以需要在放入缓冲区之前执行P(empty)
操作,在把产品放入缓冲区之后,执行V(full)
操作,表示增加了一个产品。消费者在从缓冲区取走一个产品之前,需要消耗一个产品,执行P(full)
操作,而当它取走一个产品之后,空闲缓冲区的数量会+1,执行V(empty)
因为缓冲区必须互斥的访问,所以在访问缓冲区前后分别要对 mutex
这个互斥信号量执行P和V操作。
实现互斥是在同一进程中进行一对PV操作。
实现两进程的同步关系,是在其中一个进程中执行P,另一个进程中执行V。
3.1.1、思考
我们可以思考一下,能否改变相邻P、V操作的顺序?
-
若此时缓冲区内已经放满产品,则empty=0,full=n。则生产者进程执行① 使mutex变为0,再执行②,由于已没有空闲缓冲区,因此生产者被阻塞。
-
由于生产者阻塞,因此切换回消费者进程。消费者进程执行③,由于mutex为0,即生产者还没释放对临界资源的“锁”,因此消费者也被阻塞。
-
这就造成了生产者等待消费者释放空闲缓冲区,而消费者又等待生产者释放临界区的情况,生产者和消费者循环等待被对方唤醒,出现“死锁”。
-
同样的,若缓冲区中没有产品,即full=0,empty=n。按③④① 的顺序执行就会发生死锁。
实现互斥的P操作一定要在实现同步的P操作之后。即P(empty)在前,P(mutex)在后
V操作不会导致进程阻塞,因此两个V操作顺序可以交换。
3.2、多生产者多消费者问题
桌子上有一只盘子,每次只能向其中放入一个水果。爸爸专向盘子里放苹果,妈妈专向盘子中放橘子,儿子专等着吃盘子中的橘子,女儿专等着吃盘子中的苹果。只有盘子空时,爸爸或妈妈才可向盘子中放一个水果。仅当盘子中有自己需要的水果时,儿子或女儿可以从盘子中取出水果。用PV操作实现上述过程。
我们把盘子看作大小为1,初始为空的缓冲区。将父亲母亲看作是两个生产者进程,将儿子女儿看作是两个消费者进程。
-
关系分析。找出题目中描述的各个进程,分析它们之间的同步、互斥关系。
-
进程有4个,父亲、母亲、儿子、女儿。
-
互斥关系(mutex=1):对缓冲区(盘子)的访问要互斥地进行
-
同步关系(一前一后):
-
父亲将苹果放入盘子后,女儿才能取苹果
-
母亲将橘子放入盘子后,儿子才能取橘子
-
只有盘子为空时,父亲或者母亲才能放入水果。(盘子为空这个事件可以由儿子或女儿触发,事件发生后才允许父亲或母亲放水果)
-
-
-
整理思路。根据各进程的操作流程确定P、V操作的大致顺序。(互斥:在临界区前后分别PV。同步:前V后P)
-
设置信号量。设置需要的信号量,并根据题目条件确定信号量初值。(互斥信号量初值一般为1,同步信号量的初始值要看对应资源的初始值是多少)。
分别设置 apple、orange、plate 同步信号量。
semaphore mutex = 1; // 实现互斥访问盘子(缓冲区)
semphore apple = 0; // 盘子中有几个苹果
semaphore orange = 0; // 盘子中有几个橘子
semphore 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(plate);
吃掉苹果;
son()
while(1)
P(orange);
P(mutex);
从盘子中取出橘子;
V(mutex);
V(plate);
吃掉橘子;
-
父亲进程和母亲进程需要做的就是准备一个苹果/橘子,然后将苹果/橘子放入盘子,女儿和儿子只需要从盘子中取出苹果/橘子,然后吃掉。
-
父亲进程在放入苹果之前,需要先检查这个盘子是否为空,所以在把苹果放入盘子之前,需要进行
P(plate)
来检查盘子中还可以放多少个水果,将苹果放入盘子之后,需要执行V(apple)
操作来让 apple 的值加1,来告诉女儿进程此时盘子中的苹果数量已经加1了。 -
母亲进程在放入橘子之前,需要先检查这个盘子是否为空,所以在把橘子放入盘子之前,需要进行
P(plate)
来检查盘子中还可以放多少个水果,若盘子中已有水果,则母亲进程被阻塞。若盘子中没有水果,母亲将橘子放入盘子之后,需要执行V(orange)
操作来让 orange 的值加1,来告诉儿子进程此时盘子中的橘子数量已经加1了。 -
女儿进程在取出苹果之前要判断盘子中是否有自己喜欢的水果,所以在取出苹果之前执行
P(apple)
操作来检查是否已经有苹果,在女儿将苹果从盘子中取出之后,需要执行V(plate)
操作来告诉父亲/母亲进程此时盘子已经变空了。 -
儿子进程和女儿进程类似,只是检查的时候检查的是橘子。
-
我们还需要对盘子这个缓冲区进行互斥访问,所以在访问盘子之前
P(plate)
执行P(mutex)
操作,在访问盘子之后执行V(mutex)
操作。
这个题即使不设置专门的互斥信号量mutex,也不会出现多个进程同时访问盘子的现象,因为本题中的缓冲区大小为1,在任何时刻,apple、orange、plate三个同步信号量中最多只有一个是1。因此在任何时刻,最多只有一个进程的P操作不会被阻塞,并顺序地进入临界区。
因此,如果在生产者消费者问题中遇到了缓冲区大于1,就必须专门设置一个互斥信号量mutex来保证互斥访问缓冲区
总结:在生产者-消费者问题中,如果缓冲区大小为1,那么有可能不需要设置互斥信号量就可以实现互斥访问缓冲区的功能。这不是绝对的,在考试中如果来不及仔细分析,可以加上互斥信号量,保证各进程一定会互斥地访问缓冲区。但需要注意的是,实现互斥的P操作一定要在实现同步的P操作之后,否则可能引起死锁。
3.2.1、小结
解决"多生产者-多消费者问题"的关键在于理清复杂的同步关系。在分析同步问题(一前一后问题)的时候不能从单个进程行为的角度来分析,要把“一前一后”发生的事看做是两种“事件”的前后关系。
比如,如果从单个进程行为的角度来考虑的话,我们会有以下结论:
- 如果盘子里装有苹果,那么一定要女儿取走苹果后父亲或母亲才能再放入水果
- 如果盘子里装有橘子,那么一定要儿子取走橘子后父亲或母亲才能再放入水果
这么看是否就意味着要设置四个同步信号量分别实现这四个"一前一后"的关系了?正确的分析方法应该从"事件"的角度来考虑,我们可以把上述四对"进程行为的前后关系"抽象为一对"事件的前后关系"。盘子变空事件一定要在放入水果事件之前。
- "盘子变空事件"既可由儿子引发,也可由女儿引发;
- "放水果事件"既可能是父亲执行,也可能是母亲执行。
这样的话,就可以用一个同步信号量解决问题了。
3.3、吸烟者问题
假设一个系统有三个抽烟者进程和一个供应者进程。每个抽烟者不停的卷烟并抽掉它,但是要卷起并抽调一支烟,抽烟者需要有三种材料:烟草、纸和胶水。三个抽烟者中,第一个拥有烟草、第二个拥有纸、第三个拥有胶水。供应者进程无线地提供三种材料,供应者每次将两种材料放桌子上,拥有剩下那种材料的抽烟者卷一根烟并抽掉它,并给供应者进程一个信号告诉完成了,供应者就会放另外两种材料在桌上,这个过程一直重复(让三个抽烟者轮流的抽烟)
本质上这个题也属于生产者-消费者问题,更详细的说应该是可生产多种产品的单生产者-多消费者。
-
关系分析。找出题目中描述的各个进程,分析它们之间的同步、互斥关系。·
-
桌子可以抽象为容量为1的缓冲区,要互斥访问。
- 组合一:纸+胶水
- 组合二:烟草+胶水
- 组合三:烟草+纸
- 这里我们不应该把材料看作独立的物品,而是应该把两种材料看成一种组合
-
同步关系
- 桌子上有组合一 -> 第一个抽烟者取走东西(一前一后)
- 桌子上有组合二 -> 第二个抽烟者取走东西(一前一后)
- 桌子上有组合三 -> 第三个抽烟者取走东西(一前一后)
- 发出完成信号 -> 供应者将下一个组合放到桌上
-
-
整理思路。根据各进程的操作流程确定P、V操作的大致顺序。
- 对于实现互斥很简单:在访问临界资源之前和之后执行P、V操作
- 对于实现同步:我们需要遵循前V后P这样的原则
- 前操作之后执行V操作
- 后操作之前执行P操作
-
设置信号量,设置需要的信号量,并根据题目条件确定信号量初值。(互斥信号量初值一般为1,同步信号量的初始值要看对应资源的初始值是多少)
- 桌上有组合一必须在第一个抽烟者取走东西之前,所以对于这一对同步关系来说,我们需要为它设置一个同步信号量 offer1,刚开始桌子上是没有组合一的,所以同步信号量offer1 = 0。(前V后P)
- 同理,设置信号量 offer2=0、offer3=0
- 发出完成信号必须在供应者将下一个组合放到桌上之前,发出完成信号可以由3个抽烟者任何一个触发,刚开始并没有任何一个抽烟者发出完成信号,所以同步信号量 finish = 0。
- 桌上有组合一必须在第一个抽烟者取走东西之前,所以对于这一对同步关系来说,我们需要为它设置一个同步信号量 offer1,刚开始桌子上是没有组合一的,所以同步信号量offer1 = 0。(前V后P)
供应者每次在桌上放了某种组合之后,都需要执行+1并且对3取余操作,每个循环都会使 i 的值变为0、1、2这样循环,若 i 的值为0,则供应者就把组合1放到桌上,也就是让第一个吸烟者吸烟,这样就可以实现让三个吸烟者轮流吸烟的事情。
当供应者在桌子上放入某种组合之后,它需要对这种组合对应的同步信号量执行V(offer)
操作,用来通知等待这种组合的吸烟者。
供应者把材料放到桌上之后,它需要等待抽烟者发出完成信号,所以执行 P(finish)
操作。
各个吸烟者在从桌子上拿走材料之前,需要先检查桌子上的组合是不是自己需要的材料,所以执行P(offer)
操作
当吸烟者把材料拿走并且卷烟抽掉之后,又需要向供应者发出一个完成信号来通知供应者可以开始放下一组材料,所以需要执行V(finish)
由于刚开始 finish 为0,所以开始供应者会在执行 P(finish)
操作被阻塞,直到第一个抽烟者执行 V(finish)
操作之后,供应者进程才会从阻塞态回到就绪态,然后进行下一轮的供给。
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==1)
将组合二放桌上;
V(offer2); // 通知等待这种组合的吸烟者
else if(i==2)
将组合三放桌上;
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);
3.3.1、小结
吸烟者问题可以为我们解决 "可以生产多个产品的单生产者"问题提供一个思路。值得吸取的精华是:“轮流让各个吸烟者吸烟” 必然需要 “轮流的在桌上放上组合一、二、三”,注意体会我们是如何让一个整型变量 i 实现这个 “轮流” 过程的。
若一个生产者要生产多种产品(或者说会引发多种前驱事件),那么各个V操作应该放在各自对应的 “事件” 发生之后。
3.4、读者写者问题
有读者和写者两组并发进程,共享一个文件,当两个或两个以上的读进程同时访问共享数据时不会产生副作用,但若某个写进程和其他进程(读进程或写进程)同时访问共享数据时可能导致数据不一致的错误。因此要求:
- 允许多个读者可以同时对文件执行读操作。(因为读是不会改变数据,所以多个读者可同时访问数据)
- 只允许一个写者往文件中写信息。
- 任一写者在完成写操作之前不允许其他读者或写者工作。
- 写者执行写操作前,应让已有的读者和写者全部退出
-
关系分析。找出它们之间的同步、互斥关系。
-
两类进程:写进程、读进程
-
互斥关系:写进程和写进程互斥、写进程和读进程互斥。读进程和读进程不存在互斥关系。
-
-
整理思路,根据各进程的操作流程确定P、V操作的大致顺序
-
设置信号量。设置需要的信号量,并根据题目条件确定信号量初值。(互斥信号量初值一般为1,同步信号量的初始值要看对应资源的初始值是多少)
semphore 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--以上是关于进程管理(二[2])的主要内容,如果未能解决你的问题,请参考以下文章