問題解構
具體描述
主程序中可以輸入椅子的數量、理髮師的數量(可大於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); //非必要語句,控制客人離開速度
}
運行結果
結果分析
主要分析兩個地方,一是有沒有出現理髮師和理髮師搶顧客的情況,二是各自的服務人數和總服務人數的統計有沒有出錯;經過檢查驗證與預期結果基本一致。
項目總結
-
本項目的核心點在於信號量及其PV操作,其實所有的進程同步經典例子都是一樣的;可以把信號量理解爲一把鑰匙,而PV操作則決定了你能不能拿到這把鑰匙,拿到了,你才能繼續執行,拿不到,你就得一直等待;
-
上述程序僅實現了最基本的要求,實際上後續可以摸索更多的功能,比如VIP客戶機制(引入優先級),突發情況(插隊、引入搶佔)等等,還是蠻有意思的;
-
理髮師問題是操作系統的經典問題,也是非常靈活的項目之一,好好理解其中的原理,把一個問題把玩透徹,定會受益匪淺;
-
前路漫漫,任重道遠,願大家能在OS的探索路上越走越遠!