计算机操作系统笔记 PV信号量机制解决同步和互斥

首先说明一下什么叫做PV信号量机制:
信号量,接下来用Semaphore或者S来表示.其意义是代表当前临界资源的数量.正数表示目前空闲的临界资源数量.0表示目前没有临界资源空闲,也没有进程等待临界资源.负数表示目前有多少进程在等待临界资源.

struct Semaphore
{
	int value;//信号量
	queue p;//这个信号量上阻塞的进程队列
}S;

P,V是操作系统内核中定义的两个原语,原语的意思就是说计算机运行的最小单位,在运行原语的时候是不会出现中断的.计算机程序大多都是交替进行的,但是原语程序是一次性完成的.
P原语程序的具体操作为
S-1
如果S-1以后仍然大于等于零,则进程继续进行
如果S-1以后以后小于零,则将该进程阻塞以后插入阻塞队列,然后转进程调度.(小于零,说明没有资源可以直接使用,该进程需要等待资源.但等于零的话就说明刚好够用)
代码应当是这样的

void P(struct Semaphore s)
{
	s.value = s.value - 1;//即使没有,也先声明占有一个,这个很有意义,将在例子中解释
	if(s.value<0){//加入S-1小于零的情况
		block(s.p);//将程序阻塞,并且放入s信号量的阻塞队列中
	}
	return;	
}

V原语程序的具体操作为
S+1
如果S+1后结果大于零,则继续进行.
S+1后结果小于等于零,则从该信号的等待队列中唤醒一个等待进程,然后返回原进程继续执行或者转进程调度.(小于等于零说明还有进程因为等待该资源而阻塞)

void V(struct Semaphore s)
{
	s.value = s.value+1;
	if(s.value<=0){//s+1小于等于零的情况
		wakeup(s.p)//唤醒进程队列中的第一个进程
	}
	return;

用信号量解决同步和互斥问题的思路:
步骤:
1)信号量的设置
2)给信号量赋予初值(常用的互斥和同步信号量值的大小)
3)P,V操作安排的位置(其中P的顺序不能颠倒,V的顺序任意)
注意区分:
1)公用信号量,互斥时使用的信号量(二元信号量),它仅仅允许取值为"0","1"用作互斥,它联系着一组并行进程,初值为1,每个进程1均可对之施加PV操作
2)私用信号量:一般信号量(资源信号量):它联系着一组并行进程,但其初值为0,或为某个正整数N,表示资源的数目,主要用于进程的同步.只允许拥有它的进程对之施加P操作.

当然光看这个很难以理解,那么从实际问题出发吧

生产者消费者模型
某公用仓库,每次只允许一个人进出;生产者先制造产品,再存放到仓库,随后由消费者从该仓库中取出这些产品进行消费.
假设仓库中可以存储一个产品,该如何?

	//分析:
	//消费者和生产者进程的动作不一样,所以会有两类进程
	//由于仓库必须互斥地访问,需要一个互斥信号量.生产者和消费者之间是同步的,且是单缓冲区情况,所以需要一个同步信号量
	//但是在下面地表现中,实际是这样地.生产者和消费之间地两个同步信号量,正好完成了生产者之间和消费者之间地互斥访问仓库,而生产者和消费者的互斥访问仓库,在同步情况就保证了.
	struct Semaphore Empty{
		value = 1;//仓库起初为空
		queue p = SCZ;//因为仓库不空而等待的生产者
	}//表示仓库是否为空 
	struct Semaphore Full{
		value = 0;//仓库起初为不满
		queue p = XFZ;//因为仓库不满而等待的消费者
	} //表示仓库是否为满
	void SCZ()//生产者进程
	{
		while(1){//生产者不可能就生产一次产品,所以这些动作是循环进行的
			P(Empty);//检查一下如果仓库为空则,进入生产状态,仓库状态被改为不空,其他生产者无法进入
			//生产代码
			V(Full);//将仓库填满,若有消费者进程等待,则可以唤醒一个消费者进程了
		}
	}
	void XFZ()//消费者进程
	{
		while(1){//消费者不可能就消费一次产品,所以这些动作是循环进行的
			P(Full)//检查一下仓库是否为满,如果满的话就改为不满,其他消费者无法进入;
			//消费代码
			V(Empty);//将仓库清空,若有生产者进程等待,则可以唤醒一个生产者进程了
		}
	}

假设一个仓库可以存放N个产品,又改如何呢?

	//生产者和消费者进程动作不同,所以应该有两类进程
	//所有进程都需要互斥的访问仓库,所以需要一个互斥信号量
	//生产者和消费者需要同步地进行,所以需要两个同步信号量
	//由于是多缓冲区问题,所以两个同步信号量不能完成生产者之间和消费者之间的互斥问题了.
	struct Semaphore CK{
		int value = 1;//仓库此时是否在被访问
		queue p = JC;//用来存放因为无法访问仓库所阻塞的进程
	}//用于仓库访问的互斥
	struct Semaphore K{
		int value = N;//仓库起初有N个空位
		queue p = SCZ;//用来存放因为没有空位而无法访问仓库所阻塞的进程
	}//用于产品同步
	struct Semaphore Y{
		int value = 0;//仓库中有的产品数量为零
		queue p = XFZ;//用来存放因为仓库中没有产品,所阻塞的进程
	}//用于产品同步
	void SCZ()
	{
		while(1){
			P(K);//查看是否有空位,没有就阻塞
			P(CK);//如果有空位,就查看仓库是否被占用着
			//生产代码
			V(Y);//有一个产品产生,唤醒一个因为没有产品而阻塞的进程
			V(CK);//释放仓库访问权力
		}
	}
	void XFZ()
	{
		while(1){
			P(Y);//查看是否有产品,没有就阻塞
			P(CK);//如果有产品,就产看仓库是否以访问
			//消费代码
			V(K);//有一个空位产生,唤醒一个因为没有空位而阻塞的生产者进程
			V(CK);//释放仓库访问权力
		}

我们发现每个进程开头都会有两个P()原语,这两个P原语是不能调换的.假如调换会怎么样呢?
首先,如果P(CK)在P(Y)之前,那么就有可能出现,占有了仓库所有权,却因为没有产品而阻塞,此时消费者进程无法释放仓库访问权力,其他生产者进程也就无法生产,仓库就永远没有产品,这个被阻塞却又有仓库访问权力的进程将永远被阻塞.
而现在这种情况,也有可能会提出这样的疑问,就是如果因为仓库访问权没有,而被阻塞,但是等被唤醒时,是否可能仓库中已经没有产品了呢?
这一点其实在最开始说P原语的时候就解释了,P原语首先会声明占有一个产品,即,将产品数量先-1.但实际上这个进程还没有真正的消费这个产品.在其他消费者进程看来,实际上产品的数量是他们看到的+1.有一个产品已经预先为这个运行过P(Y)的消费者进程预留了.仔细想想,会明白的.

读者-写者问题
问题描述:多个读者与多个写者之间共享数据访问区,读者读,写者写,读者和写者之间应该满足一下三个条件:
1)允许多个读者同时执行读操作
2)不允许读者和写者同时操作
3)不允许多个写者同时操作

这种一类进程之间是互斥(即会给其他所有进程加锁,其他所有进程也都会给他加锁),一类进程不是互斥(只给互斥类加锁,只有互斥类进程会给他加锁)的情况,我们可以用记录量来解决问题.不互斥类进程的第一个进程需要与互斥类进程加锁和判断互斥类是否给它加了锁,不互斥类进程的最后一个进程要给互斥类解锁.
就将寻常一个互斥类进程的PV操作,分给了,不互斥类的第一个进程§和最后一个进程(V).所以需要一个记录,来判断自身是否是第一个或者最后一个进程.怎么判断呢?就像一群人参加宴会一样,来之前,看看会场里有没有人,就可以判断自己是不是第一个,走之前看看会场里有没有人,就可以判断自己是不是最后一个.但是我们要保证一件事情,就是我们不能在抬头看完,低头记录之间,让别人进来,所以需要保证一件事情就是,在记录的时候,会场上没有人数变化,没有任何进程阻塞和唤醒的变化(当然这里所有参加宴会的人都是指读者进程).怎么做到呢?在观察和记录时将所有人都挡在门外,即阻塞所有不互斥类进程.

	//分析:
	//读者和写者的动作不同,所以需要两类进程
	//这里有写者与其他进程的互斥,所以需要一个互斥信号量
	//写者,很简单,如果没有别的进程,就将所有其它进程都阻塞
	//但是读者就比较复杂了,第一个读者需要判断和锁住写者的访问,最后一个读者需要释放写者的访问
	struct Semaphore DX{
		int value = 1;//访问区是否空闲,第一个读者在访问区不空闲时,也不可访问.因为当前没有读者访问,访问区中一定时写者.访问区起初是空闲的
		queue q = DX;//用于存放因为不可以访问访问区所阻塞的进程
	}
	struct Semaphore D{
		int value = 1;//不互斥类进程起初是可以变化的
		queue q = D;//用于存放因为保持不互斥进程不变化而阻塞的不互斥类进程
	}//为了保证记录时,不互斥类进程不变化
	int Dcount;//用于记录读者进程数量
	void DZ()
	{
		while(1){//读者也同样在读完之后可以再来阅读
			P(D);//读者进场前判断自己是否可以进场
			//进场后判断自己是第几个,并记录
			//读者数量不可以变化了
			if(Dcount==0){//如果是第一个读者
				P(DX);//判断访问区是否空闲,空闲的话就锁住所有写者进程
			}
			Dcount++;
			V(D);//读者数量可以变化了
			//读代码
			P(D);//读者结束前判断自己是否可以退场
			//读者数量已经不能变化了
			//退场前判断自己是第几个,并记录
			Dcount--;
			if(Dcount==0){//如果是最后一个读者
				V(DX);//释放访问区访问权,让写者可以访问
			}
			V(D);
		}
	}		
  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值