计算机操作系统——经典进程的同步问题

计算机操作系统——信号量机制与经典进程的同步问题

信号量机制

  • 随着发展,信号量从整型信号量经记录型信号量,进而发展为“信号量集”机制。

  • 一般来说,信号量的值与相应的资源的使用情况有关。

  • 信号量的值仅由P、V操作改变。

  • 信号量的初值大于等于0

  • S > 0表示有S个可用资源

  • S = 0表示无资源可用

  • S < 0表示S等待队列中的进程个数

P,V操作的优缺点:

  1. 优点:简单,表达能力强(用P,V操作可解决任何同步,互斥问题)
  2. 缺点:不够安全,使用不当会出现死锁;遇到复杂同步互斥问题时实现复杂。
1.信号量机制——整型信号量

最初由Dijkstra把整型信号量定义为一个用于 表示资源数目的整型量S ,它与一般整型量不同,除初始化外,仅能通过两个标准的原子操作(Atomic Operation)wait(S)和signal(S)来访问。很长时间以来,这两个操作一直被分别称为P、V操作

wait(S){
	while(S <= 0) ;    /*do no-op*/
	S--;
	}
signal(S){
	S++;
    }
  • wait(S)和signal(S)是两个原子操作,因此,在执行时是不可中断的。
  • 当一个进程在修改某信号量时,没有其他进程可同时对该信号量进行修改。
  • 在wait操作中,对S值的测试和对S–操作时都不可断。
2.信号量机制——记录型信号量

整型信号量如果S一直小于等于0,那就使处理机一直处于忙等状态。记录型信号量机制则是一种不存在“忙等”现象的进程同步机制。但在采取了“让权等待”的策略之后,又会出现多个进程等待访问同一临界资源的情况。

  • 记录型信号量机制中,除了需要一个用于代表资源数目的整型变量value外,还应增加一个进程链表指针list,用于链接上述的所有等待进程。记录型信号量是由于它采用了记录型的数据结构而得名的。
typedef struct{
	int value;
	struct process_control_block *list;
}semaphore;

wait(semaphore *S){
	S->value--;
	if(S->value<0) block(S->list);
}

signal(semaphore *S){
	S->value++;
	if(S->value <= 0) wakeup(S->list);
}

3.信号量机制——AND型信号量
  • 之前的进程互斥问题,是针对各进程之间只共享一个临界资源而言的。在有些应用场合,是一个进程需要先获得两个或更多的共享资源后才能执行其任务。
  • 假定现有两个进程A和B,它们都要访问共享数据D和E。当然,共享数据都应作为临界资源。为此,可为这两个数据分别设置用于互斥的信号量Dmutex和Emutex,并令它们的初值都是1。相应地,在两个进程中都要包含两个对Dmutex和Emutex的操作。
process A:
	wait(Dmutex);
	wait(Emutex);
process B:
	wait(Emutex);
	wait(Dmutex);
  • 若进程A和进程B按下述次序交替执行wait操作:
    process A : wait(Dmutex); 于是Dmutex=0
    process B : wait(Emutex); 于是Emutex=0
    process A : wait(Emutex); 于是Emutex=-1 ,A阻塞
    process B : wait(Dmutex); 于是Dmutex=-1 ,B阻塞

A和B处于僵持,进入死锁状态

  • AND同步机制的基本思想:将进程在整个运行过程中需要的所有资源,一次性全部地分配给进程,待进程使用完后再一起释放。
  • 对若干个临界资源地分配,采取原子操作方式:要么把它所请求的资源全部分配到进程,要么一个也不分配
Swait(S1,S2, ... ,Sn){
	while(TRUE){
		if(Si>=1 && ... &&Sn>=1){
			for(i=1;i<=n;i++) Si--;
			break;
		}else{
			place the process in the waiting queue associated with the first Si found with Si<1,and set the program count of this process to the beginning of Swait operation.
		}
	}
}

Ssignal(S1,S2, ... ,Sn){
	while(TRUE){
		for(i=1;i<=n;i++){
			Si++;
			Remove all the process waiting in the queue associated with Si into the ready queue.
		}
	}
}

经典进程的同步问题

一、生产者——消费者问题

解决互斥同步问题的主要步骤:

  1. 分析清楚题目涉及的进程间制约关系。(可能是一种也可能是两种)
  2. 设置信号量(包括信号量的个数和初值,写出信号量物理含义)
  3. 身临其境写算法(并把P,V操作加到程序适当之处)
  • 生产者消费者问题是一种同步问题的抽象描述。计算机系统中的每个进程都可以消费(使用)或生产(释放)某类资源。这里的资源可以是硬件资源,也可以是软件资源。
  • 当某一进程使用某一资源时,可以看作是消费,称该进程是消费者。而当某一进程释放某一资源时,它就相当于生产者。

生产者——消费者问题的分析:

  1. 只要缓冲区未满,生产者就可以把产品送入缓冲区。(同步)
  2. 只要缓冲区为空,消费者就可以从缓冲区中取走物品。(同步)
  3. 缓冲池一次只能有一个进程访问。(互斥)

生产者——消费者问题:信号量和变量的设置

  • 由于有界缓冲池是一个临界资源,必须互斥使用。另外,还需要设置一个互斥信号量mutex,其初值为1.
  • 应该设置两个同步信号量:一个说明空缓冲区的数目,用empty表示。初值为有界缓冲区的大小n;另一个说明已满缓冲区的数目,用full表示,初值为0。
  • 变量:缓冲池:buffer[n-1],数组下标:in,out,初值为0。
/*生产者*/
producer(){
	while(1){
		生产产品;
		P(empty);    /*请求一个空闲缓冲区*/
		P(mutex);
		把产品放入缓冲池;
		in = (m+1) mod n;
		V(mutex);
		V(full);    /*增加一个产品*/
	}
}	
/*消费者*/
consumer(){
	while(1){
		P(full);    /*消耗一个产品*/ 
		P(mutex);
		从缓冲区取出一个产品;
		V(mutex);
		V(empty);    /*增加一个空闲缓冲区*/
	}
}

生产者——消费者问题:算法结论

  • P,V操作必须成对出现。
  • 互斥的P,V出现在同一进程。
  • 同步的P,V出现在不同的进程。
  • 同步的P操作和互斥的P操作同时出现时,先写同步P操作,再写互斥P操作。

例题1:桌子上有一个盘子,最多允许存放一个水果。父亲只放苹果,母亲只放橘子;女儿只吃苹果,儿子只吃橘子。用信号量P,V写出父亲、母亲、女儿、儿子的同步算法

信号量设置:
empty(盘子空余空间)=1;apple(苹果)=0;orange(橘子)=0

父亲father

father(){
	while(1){
		P(empty);
		放入苹果;
		V(apple);
	}
}

母亲mother

mother(){
	while(1){
		P(empty);
		放入橘子;
		V(orange)
	}
}

女儿daughter

daughter(){
	while(){
		P(apple);
		吃苹果;
		V(empty);
	}
}

儿子son

son(){
	while(1){
		P(orange);
		吃橘子;
		V(empty);
	}
}

例题2:桌子上有一个盘子,最多允许存放两个水果。父亲只放苹果,母亲只放橘子;女儿只吃苹果,儿子只吃橘子。用信号量P,V写出父亲、母亲、女儿、儿子的同步算法

增加互斥信号量mutex=1

父亲father

father(){
	while(1){
		P(empty);
		P(mutex);
		放入苹果;
		V(mutex);
		V(apple);
	}
}

母亲mother

mother(){
	while(1){
		P(empty);
		P(mutex);
		放入橘子;
		V(mutex);
		V(orange)
	}
}

女儿daughter

daughter(){
	while(){
		P(apple);
		吃苹果;
		V(empty);
	}
}

儿子son

son(){
	while(1){
		P(orange);
		吃橘子;
		V(empty);
	}
}

例题3:某小型超市,可容纳50人同时购物。入口处有篮子,每个购物者从入口处拿一只篮子进入购物。出口处结账(入口和出口是两个门),并归还篮子(出、入口禁止多人同时通过),用信号量P,V写出购物者的同步算法。

由题意可以看出该题中只有互斥操作。
信号量设置:(超市剩余空间)empty=50,(入口)mutex1=1,(出口)mutex2=1

购物者shopper

shopper(){
	while(1){
		P(empty);        /*这里的P操作可以理解成请求,申请一个空闲区域*/
		P(mutex1);
		从入口进入,取一只篮子;		/*入口只能一个人通过,该动作执行的过程中,不允许其他进程进入*/
		V(mutex1);
		购物;
		P(mutex2);
		从入口出去,放下篮子;
		V(mutex2);
		V(empty);		/*这里的V操作可以理解成归还,释放一个空闲区域*/
	}
}

例题4:
在这里插入图片描述

信号量设置:
(停车的信号量)stop=0,(开门的信号量)door=0

/*司机*/
driver(){
	while(1){
		P(door);
		启动车辆;
		正常行驶;
		到站停车;
		V(stop);
	}
}
/*售票员*/
conductor(){
	while(1){
		关门;		/*售票员先关门,司机才能启动车辆*/
		V(door);
		售票;
		P(stop);
		开门;
	}
}
二、哲学家进餐问题
  • 有五个哲学家围坐在圆桌旁,桌子中央放着火锅,每人面前有一只空盘子,每两人之间放一只筷子。
  • 每个哲学家的行为是思考,感到饥饿,然后吃火锅。
  • 为了吃火锅,每个哲学家必须拿到两只筷子,并且每个人只能直接从自己的左边或右边去取筷子。

在这里插入图片描述

设chopstick[5]为5 个信号量,初值均为1

philosopheri:
	while(1){
		思考;
		P(chopstick[i]);
		P(chopstick[(i+1)%5]);
		吃火锅;
		V(chopstick[i]);
		V(chopstick[(i+1)%5]);
}

为防止死锁发生可采取得措施:

  1. 最多允许4个哲学家同时去拿起左边得筷子。
  2. 仅当一个哲学家左右两边得筷子都可用时,才允许他拿筷子。
  3. 给所有哲学家编号,奇数号得哲学家必须首先拿起左边的筷子,偶数号得哲学家则相反。
三、读者——写者问题
  • 有两组并发进程:读者和写者,共享一组数据区
  • 要求:(1)允许多个读者同时执行读操作(2)不允许读者、写者同时操作(3)不允许多个写者同时操作

读者——写者问题的问题分析:

  1. 读者和写者、写者和写者之间即写者与其他进程之间互斥访问数据区。
  2. 读者与读者可以同时访问,设置一个整型信号量readcount表示正在读的进程数目,该变量是可被多个读进程访问的临界资源。
  3. 设wmutex用于读者和写者、写者和写者之间的互斥;
  4. 设rmutex用于对readcount这个临界资源的互斥访问。
  • wmutex用于读者和写者、写者和写者之间的互斥
  • readcount表示正在读的读者数目
  • rmutex用于对readcount这个临界资源的互斥访问
  • 设有两个信号量wmutex=1,rmutex=1
  • 另设一个全局变量readcount=0
/*读者*/
while(1){
	P(rmutex);
	readcount++;
	if(readcount==1)
	P(wmutex);
	V(rmutex);
	读;
	P(rmutex);
	readcount--;
	if(readcount==0)
	V(wmutex);
	V()
}
  • 2
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
读者与写者问题是多进程并发编程经典问题,主要解决的是多个进程同时对共享数据进行读写操作时可能产生的数据不一致性问题。具体来说,该问题假设有若干个读者和一个写者进程,它们同时访问某个共享资源(如一个文件、一个数据库等),读者只读取共享资源而不修改它,而写者则修改共享资源。 由于读者和写者对共享资源的访问方式不同,因此需要对它们的访问进行调度和同步,以防止读写冲突导致数据不一致。下面是一种实现读者与写者问题的方法: ```python from threading import Lock class ReaderWriter: def __init__(self): self.read_count = 0 self.mutex = Lock() # 互斥锁,用于保护共享资源的访问 self.write_lock = Lock() # 写锁,用于保证写操作的原子性 def start_read(self): self.mutex.acquire() self.read_count += 1 if self.read_count == 1: self.write_lock.acquire() # 第一个读者获取写锁,防止写者进程修改共享资源 self.mutex.release() def end_read(self): self.mutex.acquire() self.read_count -= 1 if self.read_count == 0: self.write_lock.release() # 最后一个读者释放写锁,允许写者进程修改共享资源 self.mutex.release() def start_write(self): self.write_lock.acquire() # 写锁保证写操作的原子性 def end_write(self): self.write_lock.release() # 释放写锁 ``` 在上述代码,`ReaderWriter` 类实现了读者与写者的同步和调度。具体来说,它定义了三个方法: - `start_read`:读者开始读取共享资源时调用该方法,它首先获取互斥锁,然后增加读者计数器,如果是第一个读者,则获取写锁,以防止写者进程修改共享资源。 - `end_read`:读者读取完共享资源时调用该方法,它首先获取互斥锁,然后减少读者计数器,如果是最后一个读者,则释放写锁,允许写者进程修改共享资源。 - `start_write`:写者开始修改共享资源时调用该方法,它直接获取写锁,保证写操作的原子性。 - `end_write`:写者修改完共享资源时调用该方法,它释放写锁。 在实际使用,可以将共享资源作为 `ReaderWriter` 类的一个属性,并在读者和写者进程调用相应的方法,以实现进程同步和数据一致性。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值