1.生產者-消費者問題
1.經典問題
問題描述
一組生產者進程和一組消費者進程共享一個初始爲空,大小爲n的緩衝區,只有緩衝區沒滿時,生產者才能把消息放入緩衝區,否則必須等待,只有緩衝區不空時,消費者才能從中取出消息,否則必須等待。由於緩衝區是臨界資源,它只允許一個生產者放入消息或一個消費者從中取出消息。
問題分析
關係分析
生產者和消費者對緩衝區訪問都是互斥訪問(生產者之間互斥,避免數據覆蓋;消費者之間互斥,避免數據重複消費;生產者和消費者之間互斥)。同時,生產者和消費者之間也是相互協作的關係,只有生產者生產之後,消費者才能消費,屬於同步關係。
信號量設置
設置兩個同步信號量:full=0,表示初始滿緩衝區個數爲0個,empty=n,表示初始空緩衝區個數爲n個。
設置一個互斥信號量:mutex,初值爲1,用於控制對緩衝區的互斥訪問。
生產者生產之前判斷是否有空餘緩衝區,生產一個消息之後,
代碼示例
semaphore mutex = 1;
semaphore empty = n;
semaphore full = 0;
producer(){
while(1){
produce an item in nextp;
p(empty); //只在有空緩衝區時,才進行下一步
p(mutex); //有空緩衝區的前提下,判斷是否消費者正在消費
add nextp to buffer;
v(mutex);
v(full);
}
}
consumer(){
while(1){
p(full); //只在有滿緩衝區時,才進行下一步
p(mutex); //在有滿緩衝區的前提下,
remove an item from buffer;
v(mutex);
v(empty);
consume the item;
}
}
這種情形,比較簡單,找到同步和互斥關係,然後設置好相應的信號量,根據情況賦予初值即可(互斥信號量初值肯定爲1)。每次申請資源前,執行p操作,每次釋放資源後,執行v操作。
2.複雜生產者消費者問題
問題描述
桌子上有一個盤子,每次只能向其中放入一個水果。爸爸專向盤子中放蘋果,媽媽專向盤子中放橘子,兒子專等喫盤子中的橘子,女兒專等喫盤子中的蘋果。只有盤子爲空時,爸爸或媽媽纔可向盤子中放一個水果;僅當盤子中有自己需要的水果時,兒子或女兒可以從盤子中取出。
問題分析
關係分析
每次只能向盤子中放入一個水果,爸爸和媽媽是互斥關係。爸爸和女兒,媽媽和兒子是同步關係,而且這兩對進程必須連起來,兒子和女兒之間沒有互斥或同步關係,因爲他們是選擇條件執行,不可能併發。
信號量設置
設置兩個同步信號量:apple=0,用於協調爸爸和女兒,orange=0,用於協調媽媽和兒子
設置一個互斥信號量:plate=1,用於保證爸爸和媽媽互斥訪問盤子
代碼示例
semaphore plate=1,apple=0,orange=0;
dad(){
while(1){
prepare an apple;
p(plate);
put the apple on the plate;
v(apple);
}
}
mom(){
while(1){
prepare an orange;
p(plate);
put the orange on the plate;
v(orange);
}
}
son(){
while(1){
p(orange);
take an orange from the plate;
v(plate);
eat the orange;
}
}
daughter(){
while(1){
p(apple);
take an apple from the plate;
v(plate);
eat the apple;
}
}
這是生產者-消費者模型的升級版,本質上是兩組生產者和消費者。問題的關鍵是生產者之間的互斥,生產者和消費者之間的同步連續執行。
比如爸爸和女兒在生產之後都沒有立即釋放盤子,而是釋放對應的水果,這樣兒子和女兒都可以訪問盤子,但是隻有有相應水果的消費者纔可以順利進行消費,然後釋放盤子。
兒子和女兒訪問盤子都沒有p(plate),因爲他們不需要對盤子進行互斥訪問,兩個消費者是選擇條件執行。
找到這個關鍵點即可。
2.讀者-寫者問題
問題描述
有讀者和寫者兩組併發進程,共享一個文件,當兩個或以上的讀進程同時訪問共享數據時,不會產生副作用,但若某個寫進程和其他進程(讀進程或寫進程)同時訪問共享數據時則可能導致數據不一致的錯誤。因此要求:①允許多個讀者可以同時對文件進行讀操作;②只允許一個寫者向文件中寫信息;③任一寫者在完成寫操作之前不允許其他讀者或寫者工作;④寫者執行寫操作前,應讓已有的讀者和寫者全部退出。
問題分析
關係分析
讀者和寫者是互斥的,寫者和寫者也是互斥的,但是讀者和讀者不存在互斥問題。
寫者比較好處理,和任何進程都是互斥的,用互斥信號量的p操作和v操作即可解決。讀者的問題比較複雜,需實現與寫者互斥的同時,實現與其他讀者的同步。
可以引入一個計數器(普通變量,而非記錄型信號量,也不是整型信號量,只用於統計讀者進程數量),用於記錄當前讀者的數量,如果讀者數爲0,則需要判斷是否可以互斥訪問,如果讀者數大於0,則說明當前不存在寫者,可以直接訪問。
但是對於計數器的修改也需要互斥進行,所以需要額外引入一個互斥信號量,用戶保護計數器的修改。
信號量設置
設置兩個互斥信號量:
mutex=1用於實現計數器(普通變量)的修改,
rw=1用於實現讀者和寫者對文件的互斥訪問
代碼示例
int count=0;
semaphore mutex=1;
semaphore rw=1;
writer(){
while(1){
p(rw);
writing;
v(rw);
}
}
reader(){
while(1){
p(mutex); //對計數器的訪問進行互斥
if(count==0)
p(rw); //只在第一個讀者申請訪問時,判斷是否可以互斥訪問文件,後面的讀者直接數目加一
count++;
v(mutex);
reading;
p(mutex);
count--;
if(count==0)
v(rw); //只在最後一個讀者讀完時釋放rw鎖
v(mutex);
}
}
在上面的算法中,讀進程是具有優先權的,只有一直有讀進程在,寫進程就有可能被餓死。只有讀進程都執行完了,寫進程纔有機會訪問文件。
算法改進
當有讀者正在訪問文件時,如共有寫進程請求訪問,這時應禁止後續讀者的請求,等到已在共享文件的讀者執行完畢,立即讓寫者執行。只有在無寫者執行的情況下,才允許讀者再次執行。
代碼示例
int count=0;
semaphore mutex=1;
semaphore rw=1;
semaphore w=1;
writer(){
while(1){
p(w);
p(rw);
writing;
v(rw);
v(w);
}
}
reader(){
while(1){
p(w); //只要來了一個寫者,後續的讀者就會在這裏阻塞,當前已經在共享文件執行的進程執行完之後,釋放rw,寫者執行,然後讀者才能繼續執行。
p(mutex);
if(count==0)
p(rw);
count++;
v(mutex);
v(w);
reading;
p(mutex);
count--;
if(count==0)
v(rw);
v(mutex);
}
}
這個算法對於寫者來說還是不太公平的,如果共享文件已經有很多讀者在訪問了,這個時候新來的寫者必須等這些讀者全都運行完了之後纔可以訪問共享文件。但是這個算法改進了讀者一直佔用共享文件的問題,只要寫者進程到來,就可以佔用w,使得後續的讀進程被阻塞。
3.哲學家進餐問題
問題描述
一張圓桌上,5名哲學家,每兩名哲學家之間有一根筷子。哲學家需拿起兩根筷子纔可以進餐,否則等待。
問題分析
關係分析
每名哲學家與左右鄰居對筷子的訪問是互斥的
思路
有兩種解決辦法:
1.同時拿起兩根筷子
2.對每個哲學家拿起筷子的動作指定規則,避免飢餓和死鎖發生
信號量設置
定義互斥信號量數組chopstick[5]={1,1,1,1,1},用於對5個筷子的互斥訪問。哲學家按序編號0-4,哲學家i左邊筷子的編號爲i,右邊筷子的編號爲(i+1)%5
代碼示例
semaphore chopstick[5]={1,1,1,1,1};
pi(){
do{
p(chopstick[i]);
p(chopstick[(i+1)%5]);
eat;
v(chopstick[i]);
v(chopstick[(i+1)%5]);
think;
}while(1);
}
這個算法存在一個問題,當5個哲學家都要進餐,並且都拿起了左邊的筷子時,所有進程都無法獲取右邊的筷子,全部阻塞等待其他哲學家釋放筷子,造成死鎖。
爲防止死鎖產生,可以加一些限制條件:
①至多運行4名哲學家同時進餐,這個時候即使4個哲學家都拿起了左邊的,但是第五根筷子可以被其中一個哲學家拿起,然後正常推進。
②僅當哲學家能夠同時拿起左右兩根筷子時,才允許他拿起筷子
③對哲學家順序編號,奇數號先拿左邊再拿右邊,偶數號相反。
代碼示例
semaphore chopstick[5]={1,1,1,1,1};
semaphore mutex=1; //保證每次只有一個哲學家能夠訪問筷子
pi(){
do{
p(mutex); //如果已經有哲學家在訪問筷子,則阻塞
p(chopstick[i]); //否則直接拿起兩根筷子,順利推進
p(chopstick[(i+1)%5]);
v(mutex);
eat;
v(chopstick[i]);
v(chopstick[(i+1)%5]);
think;
}while(1);
}
不因爲有筷子能拿起就拿起,而考慮能否一次性拿起兩根筷子。這是哲學家進餐問題的關鍵。
4.吸菸者問題
問題描述
三個吸菸者,一個供應者。每個吸菸者需要的材料不同,共需要三種材料:菸草,紙,膠水。他們分別只有其中一種,剩下的兩種需要供應者提供。供應者可以無限的提供三種材料,每次只隨機提供其中的兩種(意味着每次只有一個吸菸者的材料能夠湊齊)。湊齊材料的吸菸者成功吸菸之後,供應者才能繼續提供。
問題分析
關係分析
供應者和吸菸者屬於同步關係,只有供應者供應了自己所需要的材料時,吸菸者纔可以正常消費。三個吸菸者不能同時吸菸,屬於互斥關係。
信號量設置
設置四個同步信號量,
offer1=0:第一個吸菸者需要的材料
offer2=0:第二個吸菸者需要的材料
offer3=0:第三個吸菸者需要的材料
finish=0:吸菸動作完成,供應者可以繼續提供
代碼示例
int random;
semaphore offer1=0;
semaphore offer2=0;
semaphore offer3=0;
semaphore finish=0;
//供應者
process p1(){
while(1){
random=任意一個整數隨機數;
random=random%3;
switch(random){
case 0:
v(offer1); //供應的材料不同,釋放的資源也不一樣
break;
case 1:
v(offer2);
break;
case 2:
v(offer3);
break;
}
supply the material;
p(finish);
}
}
process p2(){
p(offer1); //只在自己需要的材料釋放之後,纔可以正常推進,否則阻塞
get the material and smoke;
v(finish); //在拿到材料之後,供應者纔可以繼續提供下一組材料
}
process p3(){
p(offer2);
get the material and smoke;
v(finish);
}
process p4(){
p(offer3);
get the material and smoke;
v(finish);
}