操作系统之进程—信号量及其操作 (三)

1.同步与同步机制 

  • 著名的生产者--消费者问题是计算机操作系统中并发进程内在关系的一种抽象,是典型的进程同步问题。
  • 生产者进程可以是计算进程、发送进程;
  • 消费者进程可以是打印进程、接收进程等等。
  • 解决好生产者--消费者问题就解决好了一类并发进程的同步问题 

生产者-消费者案例

有一环形缓冲池,包含n个缓冲区(0~n-1)。有两组进程:一组生产者进程和一组消费者进程,生产者进程向空的缓冲区中放产品,消费者进程从满的缓冲区中取走产品

生产者-消费者问题

P1、P2(生产者线程)各生产出一个产品,系统共有两个产品

  • 正确的结果:这两个产品应被放在buffer[0 ] (=‘A’)和buffer[1 ] (=‘B’)两个缓冲区中,最后in =2, counter = 2
  • 实际上的结果: buffer[0 ] =‘B’, buffer[1 ] 和buffer[2 ] 均为空, in = 2, counter = 2
  • 出错原因(1): in 、 out均为临界资源,应互斥访问,即生产者进程互斥访问in,消费者进程互斥访问out,它们均互斥访问buffer。一次只允许一个进程访问,对in 、 out 的访问不能被打断
  • 出错原因(2):各类进程访问缓冲区的速率不同,要得到正确结果,需要调整并发进程的速度,这需要通过在进程间交换信号或消息来调整相互速率,达到进程协调运行的目的。这种协调过程称为进程同步 如:生产者需要在缓冲区中有空位才能生产; 消费者需要缓冲区中有产品才能消费;

同步机制

操作系统实现进程同步的机制,它通常由同步原语组成

常用的同步机制

  1. 信号量与PV操作
  2. 管程
  3. 消息传递 

2.信号量与PV操作

 将交通管制中多种颜色的信号灯管理交通的方法引入操作系统,让两个或多个进程通过特殊变量展开交互。

  • 信号量(Semaphore) :一个进程在某一特殊点上被迫停止执行,直到接收到一个对应的特殊变量值。
  • 进程可以使用P、V两个特殊操作来发送和接收信号

原语

原语是操作系统内核中执行时不可中断的过程,即原子操作

信号量特点 

  • 信号量起信号灯的作用;
  • 操作系统中,信号量是用来表示物理资源的实体,信号量是一种软资源;
  • 除赋初值外,信号量仅能由同步原语对其进行操作,没有任何其他方法可以检查和操作信号量。即只能有P、V操作改变信号量。

两个信号量的操作原语

  • P操作原语: passeren,中文译为"通过"
  • V操作原语: vrijgeven,中文译为"释放"
  • 常用的其他符号有:P和V;up和down;signal和wakeup等

信号量+pv

利用信号量和P、V操作既可以解决并发进程的竞争问题,又可以解决并发进程的协作问题

3.信号量的分类

信号量按其用途分为两种

  • 公用信号量:初值常常为1,用来实现进程间的互斥。
  • 私有信号量:初值常常为可用资源数,多用来实现进程同步。拥有该信号量的一类进程可以对其执行P操作,而另一类进程可以对其执行V操作。

信号量按其取值分为两种

  • 二元信号量:仅取0和1 ,主要用于解决进程互斥问题;
  • 一般信号量:允许取值为非负整数,主要用于解决进程同步问题。

信号量的形式

随着信号量的发展,先后出现了以下几种形式的信号量:整型信号量、结构型信号量、信号量集机制

整型信号量

(1)数据类型:整型量S

(2)操作:

  • 初始化
  • 原子操作P(S)
  • 原子操作V(S)
  • 除初始化外,仅能通过P、V操作访问

(3)原子操作:操作不能被中断

(4) P(S)、V(S)操作定义

  • P(S): while  S≤0  do  no-op    
  •  S:=S-1;
  • V(S): S:=S+1;

(5) S初值:一般取可用资源数目

缺陷:未遵循“让权等待”原则 若S=0,则进程便在while  S≤0  do  no-op无限循环进行等待,白白浪费CPU时间 为实现让权等待(当进程不能进入临界区,应该立即释放处理机,防止进程忙等待),引进了结构性信号量机制 

结构型信号量

(1)解决问题:让权等待

(2)办法:等待时阻塞自己

(3)数据结构

typedef struct semaphore {
    int value;//可用资源数
    struct pcb *list ;//阻塞队列
}

P(S)、V(S)操作定义
procedure  P(semaphore &s){
    s. value-- ;
    if (s . value < 0)block(s.list);
}

procedure V (semaphore &s){
    s . value ++ ;
    if  (s . value ≤ 0)wakeup(s. list) ;
}

  • P(s)和V(s)中的s为两个过程的共享变量
  • s的取值:信号量s的初值在初始化时,根据其用途进行设置
  • 推论1:s .value>=0时, s. value值表示还可以执行P而不会被阻塞的进程数,每执行一次P就意味着一次分配一个单位的资源
  • 推论2:当 s. value<0,表示没有可用资源,请求该资源的进程被阻塞。 s. value的绝对值表示被阻塞的进程数,执行一次V就意味着释放一个资源。若s. value<0表示还有被阻塞的进程,需要唤醒一个被阻塞的进程,使他到就绪队列中排队
  • 推论3:通常,P操作意味着请求一个资源,V操作意味着释放一个资源;特定条件下P操作代表挂起进程的操作,V操作代表唤醒被挂起进程的操作

 AND型信号量

(1)解决问题

克服整型及记录型信号量一次仅能获得一个单位的一种临界资源(一个单位是指一个信号量+对应的pv操作),从而容易导致死锁(违背有限等待原则)的缺陷 (如果有多个信号量容易导致死锁)。

(2) AND 同步机制思想:对于进程在整个运行过程中需要的所有资源,要么一次性地全部分配给该进程,要么一个也不分

(3)数据结构:类同记录型信号量的数据结构

(4)操作定义

Swait(S1 ,  S2 , … ,  Sn )
		if S1≥1 and … and  Sn≥1  then 
		      for  i:=1 to  n do
		           Si := Si -1;
		       endfor
		else 
		将进程放在与Si相关联的阻塞队列,Si是最先发现小于1的,并且将进程的程序计数器置于Swait操作的开头;
		endif


Ssignal(S1 ,  S2 , … ,  Sn )
		for  i:=1 to  n do
		 Si := Si + 1;
		将与Si相关联的阻塞队列中的所有进程移出放入到就绪队列;
	 	endfor 

信号量集 

(1)解决问题: 某进程一次需要N个某类临界资源时,避免执行N次P操作 当资源量<下限值时不分配

(2)数据结构:信号量S,需求值d,下限值t

(3)操作定义

Swait(S1 , t 1,d1; … ;  Sn , tn  , dn )
		if S1≥ t 1 and … and  Sn≥ tn then 
		for  i:=1 to  n do
		 Si := Si - di;
		endfor
		else 
		将进程放在与Si相关联的阻塞队列,Si是最先发现小于ti的,并且将进程的程序计数器置于Swait操作的开头;
		endif

Ssignal(S1 ,d1 , … ,  Sn ,dn )
		for  i:=1 to  n do
		 Si := Si + di ;
将与Si相关联的阻塞队列中的所有进程移出放入到就绪队列;
	 	endfor 

4.信号量实现互斥

方法

为临界资源设一互斥信号量mutex,初值为1,将临界区置于P(mutex)和V(mutex)之间, P(mutex)和V(mutex)一定成对出现在同一个进程中 

 对于两个并发进程,互斥信号量的值仅取1、0和-1三个值

  • 若MUTEX=1表示没有进程进入临界区
  • 若MUTEX=0表示有一个进程进入临界区
  • 若MUTEX=-1表示一个进程进入临界区,另一个进程等待进入。 

5.信号量解决5位哲学家吃通心面问题

问题描述

五位哲学家围坐一张圆桌,桌上放五根筷子,每位哲学家只能拿起与他相邻的两根筷子吃饭 哲学家的生活方式是交替地进行思考和进餐

利用结构型信号量解决哲学家进餐问题

  • 数据结构:每根筷子都是一个临界资源,都应定义一个信号量,为五根筷子定义一个信号量数组,每个信号量的初值为1
  • 算法实现:
操作描述:第i位哲学家的活动如下: 
Semaphore fork[5];
for (int i=0;i<5;i++) fork[i]=1;
Cobegin
Process philosopher_i(){
             while(true){ 
                        think();
		P(fork [ i ]);
		P(fork [(i+1) mod 5]);
		eating ;
		V(fork [ i ]);
		V(fork [(i+1)mod 5]);	
		thinking ;
	}
}
coend

结构型信号量缺点

若每个哲学家都各自拿起他左边的一根筷子,然后再去拿他右边的筷子时,将都拿不到右边的筷子,大家又都不会放下手中的筷子,大家在相互等待别人释放筷子,系统于是进入死锁状态 

死锁问题解决方案

1)至多允许有四位哲学家同时去拿左边的筷子,最终保证至少有一位哲学家能够进餐,(至多可以有两位哲学家能够进餐)

此时已经有四位哲学家拿起了筷子,所以哲学家4不能再拿筷子了,哲学家3可以拿起筷子4,从而拥有两根筷子可以吃饭

2)仅当哲学家的左右两根筷子均可用时,才允许他拿起筷子进餐。这种方案可以采用AND信号量机制来实现

3)规定奇数号哲学家先拿他左边的筷子,然后再去拿右边的筷子;偶数号哲学家先拿他右边的筷子,然后再去拿左边的筷子

利用AND型信号量解决哲学家进餐问题

Semaphore fork[5];
for (int i=0;i<5;i++) fork[i]=1; 
Cobegin
Process philosopher_i(){
	   while(true){  
                  think ;
		Swait(fork[(i+1)mod 5],fork[i]);
	            eat ;
	   	Ssignal(fork[(i+1) mod 5],fork[i]);
	}
}
coend

5.信号量解决生产者-消费者问题

数据结构:

  • 1)含有n个缓冲区的公用缓冲池
  • 2)互斥信号量mutex:实现诸进程对缓冲池的互斥使  用,一次仅允许一个进程读或写公用缓冲池,初值为1
  • 3)资源信号量empty:记录空缓冲区个数,初值为n
  • 4)资源信号量full:记录满缓冲区个数,初值为0

操作要求:多个生产者进程之间、多个消费者进程之间、生产者进程与消费者进程之间均能正确同步

操作描述:

注意:

1)在每个程序中用于互斥的P(mutex)和V(mutex)必须成对出现。

2)对资源信号量empty和full的操作也同样必须成对出现,但它们在不同的程序中。

3)在每个程序中的多个P操作顺序不能颠倒,否则容易引起死锁 ,进程应先申请资源信号量p(empty)/p(full),再申请互斥信号量p(mutex)!

PV操作信号量的疑问

  1. s. value大于0那就表示有临界资源可供使用,为什么不唤醒进程?

S大于0的确表示有临界资源可供使用,而且这个时候没有进程被阻塞在这个资源上,也就是说没有进程因为得不到这类资源而阻塞,所以没有被阻塞的进程,自然不需要唤醒。

  1. s. value小于0应该是说没有临界资源可供使用,为什么还要唤醒进程?

V原语操作的本质在于:一个进程使用完临界资源后,释放临界资源,使S加1,以通知其它的进程,这个时候如果S<0,表明有进程阻塞在该类资源上,因此要从阻塞队列里唤醒一个进程来“接手”该类资源。比如,有两个某类资源,四个进程A、B、C、D要用该类资源,最开始S=2,当A进入,S=1,当B进入S=0,表明该类资源刚好用完, 当C进入时S=-1,表明有一个进程被阻塞了,D进入,S=-2。当A用完该类资源时,进行V操作,S=-1,释放该类资源,而这时S<0,表明有进程阻塞在该类资源上,于是唤醒一个。

猜你喜欢

转载自blog.csdn.net/m0_37834471/article/details/83717880