【操作系統】基於信號量的多理髮師問題實現

問題解構

具體描述

主程序中可以輸入椅子的數量、理髮師的數量(可大於1)以及顧客流量(10~20),多個顧客線程和理髮師線程應該能夠正確的併發執行。程序應輸出併發執行的過程,能夠正確統計並顯示每個理髮師服務的顧客數,以及因無座位直接離開的顧客數。

要求剖析

輸入變量 + 理髮師問題 + 併發執行 = 多理髮師問題

基本思路

通過信號量的設置來解決阻塞;多理髮師共享顧客隊列。

算法原理

  • 所有理髮師使用相同的程序段
  • 所有顧客使用相同的程序段
  • 使用自旋鎖保證理髮師和理髮師互斥
  • 使用互斥鎖保證理髮師和顧客互斥

相關調用

  • Linux信號量工具:
    #include<semaphore.h>中定義了很多信息量操作中常用的數據結構和系統函數,下面羅列本次實驗將用到的:
    sem_t:具體信號量的數據結構
    sem_init :用於創建信號量,並能初始化它的值
    sem_wait:相當於wait操作
    sem_post:相當於signal操作
  • POSIX線程相關:
    #include<pthread.h>中用到的數據類型和函數如:
    pthread_t:用於聲明線程ID
    pthread_create :創建一個線程
    pthread_join:阻塞當前線程,直到另外一個線程運行結束
  • 常用標準庫等

項目方案

操作環境

  • VMware Workstation Pro
  • Ubuntu 18.04.2 LTS

流程圖

  • 多線程流程
    多線程
  • 算法流程
    算法設計

數據結構

int waiting=0;  //等待中的顧客數
int working=0;  //統計理髮師情況
int leave = 0;  //因沒有座位而直接離開的顧客數
int served[] = {0};  //統計各個理髮師服務的顧客數
Int count = 0; //統計理髮師服務的總顧客數
semaphore customer=0;  //是否有顧客,用於理髮師和顧客之間的同步信號量
semaphore barber=0;  //理髮師狀態,用於顧客之間互斥使用理髮師資源
semaphore worker = BNUM;  //自旋鎖
semaphore mutex=1;  //理髮師和理髮師之間的互斥鎖
semaphore mutex2=1;  //顧客與理髮師之間的互斥鎖

僞代碼

  • 顧客進程
    //顧客進程沒有while
    wait(mutex2);
    if(waiting==N) 
    {//離開;signal(mutex2);}
    else
     { //進店坐下;
       waiting++;
       if(waiting==1&&working<0)
       //第一位顧客
       working++;
       signal(customer);
       signal(mutex2);
       wait(barber); //測試理髮師
       //正在理髮
       //離開
      }

  • 理髮師進程
   //理髮師
   while(true)
   {
     wait(worker);
     wait(mutex);
     if(waiting==0) {
       working--;
       signal(worker);
       signal(mutex);
       wait(customer)}
     else{
       signal(barber);
       //爲一位顧客理髮;
       waiting--;
       signal(mutex);
       signal(worker);
      }
       //顧客理髮結束; 
    }

測試數據

  • 固定測試數據(調試用)
    ① 10張椅子;2名理髮師;20位顧客
    ② 5張椅子;5名理髮師;10位顧客
    ③ 10張椅子;10名理髮師;8位顧客
  • 任意/隨機測試數據(測試用)

預期輸出結果

依次輸入設置變量;
開店;
沒有顧客,N名理髮師正在睡覺;
第0號顧客進店;
第0號顧客坐下等待理髮;
等待人數+1;
第0號理髮師被喚醒;
第0號理髮師開始理髮;
工作人數+1;
第0號顧客正在被理髮;
等待人數-1;
第1號顧客進店;
。。。。。。
第X號顧客進店;
第X號顧客坐下等待理髮;
等待人數+1;
第X+1號顧客進店;
第X+1號顧客坐下等待理髮;
等待人數+1;
。。。。。。
第Y號顧客進店;
無空位,顧客離開;
離開人數+1;
。。。。。。
第M號顧客理髮結束;
服務人數+1;
。。。。。。
沒有顧客,N名理髮師正在睡覺;
關店;
顧客總數,離開總數;

部分源碼

void *barber(void *arg) //理髮師線程
{
    while(1){
    	struct timeval tv;
    	gettimeofday(&tv, NULL);
    	srand(tv.tv_sec + tv.tv_usec + getpid());  //毫秒級種子
    	CUT_TIME = rand()%10001;

    	sem_wait(&worker);
    	sem_wait(&mutex);
    	if(waiting == 0) 
    	{
    	    //沒有顧客,理髮師睡覺,等待cus信號	
    		printf("********沒有顧客,第 %ld 號理髮師正在睡覺!*********\n",(unsigned long )arg);		
    		working--; 
    		sem_post(&mutex);
    		sem_post(&worker);
    		sem_wait(&cus); //等待顧客進店
    	}
    	else 
    	{ 
    	    //喚醒一位顧客開始理髮
    		sem_post(&bar);
    		waiting--;
    		//統計人數
    		printf("第 %ld 號理髮師開始理髮,已服務人數:%d\n",(unsigned long )arg,served[(unsigned long )arg]);
    		//printf("本次理髮時間:%d\n",CUT_TIME); 
    		printf("一位顧客正在理髮,等待理髮的顧客數: %d\n",waiting);
    		sem_post(&mutex); //保證理髮師之間互斥
    		//理髮
    		usleep(CUT_TIME); //非必要語句,控制理髮速度,模擬理髮師的效率,程序執行過程與該值密切相關.
    		printf("一位顧客理髮結束!\n");
    		count++;
    		served[(unsigned long )arg]++;
    		sem_post(&worker); //保證理髮師只能同時爲一位顧客理髮
     }     
  }
}

void *customer(void *arg) //顧客線程
{
	struct timeval tv;
    gettimeofday(&tv, NULL);
    srand(tv.tv_sec + tv.tv_usec + getpid()); //毫秒級種子
	LEAVE_TIME = rand()%11;
 	sem_wait(&mutex2); //互斥鎖
    printf("第 %ld 號顧客進店...\n",(unsigned long )arg);

    if(waiting == SNUM) //沒有空位,顧客離開.
    {
        //統計離開人數
    	leave++;
    	sem_post(&mutex2);
    	printf("沒有座位,第 %ld 號顧客離開!離開人數:%d\n",(unsigned long )arg,leave);    
    }
    else
    {
        //統計等待人數
    	waiting++;
    	printf("第 %ld 號顧客坐下等待理髮,等待理髮的顧客數:%d\n",(unsigned long )arg,waiting);
    	if(waiting == 1 && working < 0)   //如果是第一位顧客,喚醒理髮師,喚醒之後工作到沒有顧客爲止
    	{  
    	    //喚醒理髮師
        	printf("一位理髮師被喚醒,正在準備理髮!\n");
            //統計工作理髮師人數
        	working++;
            //printf("目前工作的理髮師爲:%d\n",BNUM+working);
        	sem_post(&cus);
        }
        sem_post(&mutex2);
        sem_wait(&bar);
        //等待理髮師
    }

    usleep(LEAVE_TIME);  //非必要語句,控制客人離開速度  
}

運行結果

截圖1
截圖2
截圖3

結果分析

主要分析兩個地方,一是有沒有出現理髮師和理髮師搶顧客的情況,二是各自的服務人數和總服務人數的統計有沒有出錯;經過檢查驗證與預期結果基本一致。

項目總結

  • 本項目的核心點在於信號量及其PV操作,其實所有的進程同步經典例子都是一樣的;可以把信號量理解爲一把鑰匙,而PV操作則決定了你能不能拿到這把鑰匙,拿到了,你才能繼續執行,拿不到,你就得一直等待;

  • 上述程序僅實現了最基本的要求,實際上後續可以摸索更多的功能,比如VIP客戶機制(引入優先級),突發情況(插隊、引入搶佔)等等,還是蠻有意思的;

  • 理髮師問題是操作系統的經典問題,也是非常靈活的項目之一,好好理解其中的原理,把一個問題把玩透徹,定會受益匪淺;

  • 前路漫漫,任重道遠,願大家能在OS的探索路上越走越遠!

  • 完整源碼地址

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章