操作系统进程同步习题记录

奇偶数生产者-消费者问题

三个进程 P1、P2、P3 互斥使用一个包含 N(N>0)个单元的缓冲区。

P1 每次用 produce()生成一个正整数并用 put()送入缓冲区某一个空单元中;

P2 每次用 getodd()从该缓冲区中取出一个奇数并用 countodd()统计奇数个数;

P3 每次用 geteven()从该缓冲区中取出一个偶数并用 counteven()统计偶数个数。

请用信号量机制实现这三个进程的同步与互斥活动, 并说明所定义的信号量的含义。

答:想象P1为生产者,P2、P3为消费者。

P1、P2共享缓冲区的奇数部分,故设置同步信号量odd;

P1、P3共享缓冲区的偶数部分,故设置同步信号量even;

P1、P2、P3共享整个缓冲区,用一个互斥mutex来保护,再设置同步信号量empty,表示缓冲区空位资源。

伪代码如下:

#define N 100
Semaphore empty = N;  // 假设初始条件下缓冲区有N个空位
Semaphore mutex = 1;
Semaphore odd = 0;
Semaphore even = 0;

void P1(){
  int integer;
  while(true){
    integer = produce(); 	// 生成一个整数
    P(empty); 						// down(empty),若empty为0则会被阻塞(等待别人拿走)
    P(mutex);							// 开始互斥,down(mutex)
    put();								// 放入缓冲区
    V(mutex);							// 访问临界区结束,up(mutex)
    if(integer %2 == 0){
      V(even);						// 是偶数
    } else {
      V(odd);							// 是奇数
    }
  }
}

void P2(){
  while(true){
    P(odd);							// 请求一个奇数,down(odd)
    P(mutex);						// 互斥
    getodd();
    V(mutex);
    V(empty);						// 缓冲区多一个位置,up(empty)
    countodd();
  }
}

void P3(){
  while(true){
    P(even);						// 请求一个偶数,down(even)
    P(mutex);						// 互斥
    geteven();
    V(mutex);
    V(empty);						// 缓冲区多一个位置,up(empty)
    counteven();
  }
}

野人吃肉

一个野人部落从一个大锅中一起吃炖肉,这个大锅一次可以存放 M 人份的炖肉。

当野人们想吃的时候,如果锅中不空,他们就自助着从大锅中吃肉。如果大锅空了,他们就叫 醒厨师,等待厨师再做一锅肉。

答:野人和厨师共享缓冲区锅,设置互斥信号量mutex;

厨师一次性往锅里放M份肉,设置厨师信号量cook,整形变量meat记录剩下的肉数量。

野人没吃完时,厨师在睡觉,设置野人信号量wild;

当大锅空的时候,野人不能够调用 getServingFromPot() ;

仅当大锅为空的时候,大厨才能够调用 putServingsInPot()

#define M 100
Semaphore mutex = 1;
Semaphore cook = 0, wild = 0;
int meat = 0;
void wildman(){
   while (true){
    P(mutex);
		if (meat > 0){				// 锅里还有肉
    	getServingFromPot();
  	 	eat();
      meat = meat - 1;
      V(mutex);						// 吃完一份,释放缓冲区占用
    } 
    else{
      V(wild);						// 唤醒睡觉的厨师
      P(cook);						// 请求一个厨师加肉,若厨师为0则被阻塞(等待煮肉)
      V(mutex);
    }
	}
}

void Cook(){
  while (true) { 
    P(mutex);
    if (meat == 0){ 			// 煮肉ing
      putServingsInPot(M);
      meat = meat + M;
      V(mutex);
      V(cook);					 	// 厨师做好了,可以唤醒野人等待进程继续吃
    }
    else{
      // 锅里不空,不用煮肉,睡觉(野人为0,没有人叫他,阻塞)
      P(wild);
    }
	}
}


缓冲区产品

系统中有多个生产者进程和消费者进程,共享用一个可以存 1000 个产品的缓冲区(初始为空),当缓冲区为未满时,生产者进程可以放入一件其生产的产品,否则等待;

当缓冲区为未空时,消费者进程可以取走一件产品,否则等待。

要求一个消费者进程从缓冲区连续取出 10 件产品后,其他消费者进程才可以取产品,请用信号量 P,V 操作实现进程间的互斥和同步,要求写出完整的过程;并指出所用信号量的含义和初值。

答:生产者可以一直不停地放,除非满了则阻塞,设置同步信号量empty表示空的数量,同步信号量stuff表示产品的数量;

生产者和消费者共享一个缓冲区,设置互斥信号量mutex;

有所不同的是,一个消费者必须一次拿1件产品,连续拿10次,读10次缓冲区,该过程不可中断。

#define N 1000;
Semaphore mutex = 1;
Semaphore mutex_get = 1;
Semaphore empty = N;
Semaphore stuff = 0;

void producer(){
  while(true){
    P(empty);				// 若empty为0没有空闲则等待
    P(mutex);
    puts();					// 放入一件产品
    V(mutex);
    V(stuff);				// 释放产品,可以唤醒消费者等待拿走
  }
}

void consumer(){
  int i = 0;
  while(true){
    // 当一个消费者P(mutex)时其他消费者P(mutex)会被阻塞(等待别人拿完)
    P(mutex);				// 占用缓冲区直到拿走10个再释放
    for(i = 0; i < 10; i++){
      P(stuff);			// down(stuff),产品不足会被阻塞
      P(mutex_get);
      gets();				// 取走一件物品
      V(mutex_get);
      V(empty);		  // up(empty)
    }
    V(mutex);				// 释放互斥
  }
}

寿司店问题

假设一个寿司店有 5 个座位,如果你到达的时候有⼀个空座位,你可以立刻就坐。但是如 果你到达的时候 5 个座位都是满的有⼈已经就坐,这就意味着这些人都是一起来吃饭的,那么你需要等待所有的⼈一起离开才能就坐。编写同步原语,实现这个场景的约束。

  • 使用整型变量eatingwaiting记录在寿司店就餐和等待的线程,使用信号量mutex对这两个变量的读写进行保护
  • 使用信号量queue表示排队
  • 使用布尔变量must_wait表示寿司店现在满座,新来的顾客必须等待
int eating = 0, waiting = 0;
Semaphore mutex = 1, queue = 1;
bool must_wait = false;

Customer(){
  P(mutex);
  if (must_wait){
    waiting++;
    V(mutex); //对waiting变量的保护可以释放
    P(queue);	// 被阻塞,坐着等待排队,等待被唤醒
  }
  else {
    eating++;
    must_wait = (eating == 5) 
    // 一旦我是最后一个来坐下吃导致人满的就要等所有人一起吃完,好难过
    V(mutex);	// 对eating变量的保护可以释放
  }
  // 上一部分已经解决了进店后是等待还是吃的问题
  Eat_sushi();// else的人和被唤醒的排队者成功进入这一步
  P(mutex);   // 开启对eating, waiting变量保护
  eating--;		// 吃的人-1,如果5个没全吃完,不可以换下一批人吃
  if (eating == 0){ // 最后一个吃完的人离开才可以进顾客
    int n = min(5, waiting);	// 放顾客进来的数量,不超过5个
    waiting -= n;
    eating +=n;
    must_wait = (eating == 5)
    for(int i = 0; i<n; i++){
      V(queue);  // 唤醒排队的n个人继续进程
  }
  V(mutex);	// 允许下一个吃完的人对变量和队列进行操作
}


搜索-插入-删除问题

三个线程对一个单链表进行并发的访问,分别进行搜索、插入和删除。搜索线程仅仅读取链表,因此多个搜索线程可以并发。

插入线程把数据项插入到链表最后的位置;多个插入线程必须互斥防止同时执行插入操作。但是,一个插入线程可以和多个搜索线程并发执行。

最后,删除线程可以从链表中任何一个位置删除数据。 一次只能有一个删除线程执行;删除线程之间,删除线程和搜索线程,删除线程和插入线程都不能同时执行。

请编写三类线程的同步互斥代码,描述这种三路的分类互斥问题。

  • 这个问题与读者写者有些类似
Semaphore insertMutex =1, searchMutex = 1; // 保护int变量
Semaphore No_search = 1; // 顾名思义,为1时没有搜索进程访问
Semaphore No_insert = 1; // 为1时没有插入进程访问
//当上述两个信号量同时为1,删除者才可以进行删除操作

int searcher = 0, inserter = 0;
void Search(){
  P(searchMutex);
    searcher++;
    if (searcher == 1)	// 第一个进来的搜索者加锁
      P(No_search)
  V(searchMutex);
  Searching(); // 访问临界区,多个搜索无需互斥
  P(searchMutex);
  	searcher--;
  	if (searcher == 0)
      V(No_search);	 // 表示此时没有搜索线程在进行,解锁
  V(searchMutex);
}

void Insert(){
  P(insertMutex);
  	inserter++;
  	if (inserter == 1)
      P(No_insert)
  V(insertMutex);
  
  P(insertMutex); // 既然可以和搜索线程并行,那么不用管Searcher
  	Inserting();	// 访问临界区,多个插入者要互斥访问,一次一个insert
  V(insertMutex);
  
  P(insertMutex);
  	inserter--;
  	if (inserter == 0)
      V(No_insert); // 解锁,可唤醒删除者
  V(insertMutex);
}

void Delete(){	  // 删除线程与其他任何线程互斥
  P(No_search);
  	P(No_insert); // 若为1则可进入,这个信号量顺便也可以当作删除者的互斥保护
  		Deleting();	// 搜索和插入线程都没,成功进入临界区
  	V(No_insert);
  V(No_search);	
}

上一篇:《剑指offer》第五十九题II:队列的最大值


下一篇:基于vue优化之图片懒加载