操作系統 經典同步問題
生產者—消費者問題
-
問題描述
一組生產者進程和一組消費者進程共享一個初始為空、大小為 n 的緩沖區,只有緩沖區沒滿時,生產者才把消息放入緩沖區,否則必須等待;只有緩沖區不空時,消費者才能從中讀取消息,否則必須等待。由於緩沖區是臨界資源,它只允許一個生產者放入消息,或一個消費者從中取出消息。
-
問題分析
-
關系分析:
生產者和消費者對緩沖區的訪問屬於互斥關系,而針對“消息”則生產者和消費者屬於協作關系,只有生產者生產了消息,消費者才能使用消息,因此又是同步關系。
-
思路:
兩組進程存在互斥和同步關系,也就是要解決互斥和同步PV的操作的位置。
-
信號量設置:
設置一個 mutex 為互斥信號量,用於控制互斥訪問緩沖池,初值設為 1;
信號量 full 用於記錄當前緩沖池中的“滿”緩沖區數,初值為 0;
信號量 empty 用於記錄當前緩沖池中“空”的緩沖區數,初值為 n;
-
-
進程描述
seamphore mutex=1; //臨界區互斥信號量 seamphore empty=n; //空閑緩沖區 seamphore full=0; // 緩沖區初始化為空 producer(){ // 生產者進程 while(1){ produce an item in nextp; // 生產數據 P(empty);(用什么,p一下) // 獲取空緩沖區單元 P(mutex);(互斥夾緊) // 進入臨界區 add nextp to buffer; (行為) //將數據放入緩沖區 V(mutex);(互斥夾緊) // 離開臨界區,釋放互斥信號量 V(full);(提供什么,V一下) //滿緩沖區數加1 } } consumer(){ //消費者進程 while(1){ P(full); //獲取滿緩沖區單元 P(mutex); //進入臨界區 remove an item from buffer; // 從緩沖區取出數據 V(mutex); // 離開臨界區,釋放互斥信號量 V(empty); // 空緩沖區數加 1 consume the item; //消費數據 } }
-
Note
- 兩個進程中兩個P操作順序不能顛倒,顛倒可能發生以下情況:empty=0,mutex=1時producer執行P(mutex)進入臨界區后P(empty)進行阻塞,此時consumer執行P(mutex)也阻塞,從而導致無休止的等待
- 兩個進程中兩個V操作順序可以顛倒
生產者—消費者問題(多個生產者和消費者)
-
問題描述
桌子上有一個盤子,每次只能向其中放入一個水果。爸爸專向盤子中放蘋果,媽媽專向盤子中放橘子,兒子專吃盤子中的橘子,女兒專等吃盤子中的蘋果。只有盤子為空時,爸爸或媽媽才可以向盤子中放一個水果;僅當盤子中有自己需要的水果時,兒子或女兒可以從盤子中取出。
-
問題分析
-
關系分析:
爸爸和媽媽是互斥關系,爸爸和女兒、媽媽和兒子是同步關系,而且這兩對進程必須連起來,兒子和女兒之間沒有互斥和同步關系,因為他們是選擇條件執行。
-
思路:
這里一共由4個進程,可抽象為兩個生產者和兩個消費者被連接到大小為1的緩沖區上。
-
信號量設置:
信號量 plate 表示互斥信號量,用於確定是否可以往盤子中放水果,初值為 1 表示允許放入一個;
信號量 apple 表示盤中是否還有蘋果,初值為 0表示沒有不許取;
信號量orange 表示盤中是否有橘子,初值同樣為 0,orange=1 表示盤子中由橘子允許取。
-
-
進程描述
semapore 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 aplle from the plate; V(plate); eat the apple; } }
-
Note:此時plate=1,無需設置mutex,但是仍然存在互斥關系
讀者-寫者問題
-
問題描述
有讀者和寫者兩組並發進程,共享一個文件,當兩個或兩個以上的讀進程同時訪問共享數據時不會產生副作用,但若某個寫進程和其他進程同時訪問共享數據時則可能導致數據不一致的錯誤,因此:
① 允許多個讀者可以同時對文件執行讀操作;
② 只允許一個寫者往文件中寫信息;
③ 任一寫者在完成寫操作之前不允許其他讀者進程或寫者工作;
④ 寫者執行寫操作前,應讓已有的讀者和寫者全部退出。 -
問題分析
-
關系分析
讀者和寫者互斥的,寫者和寫者互斥,讀者和讀者之間不互斥。
-
思路
兩個進程,讀者和寫者。由於寫者和其他進程都互斥,因此可用互斥信號量的P操作、V操作解決;讀者則較為復雜,與寫者互斥的同時,又要與其他讀者同步,需要一個計數器用於判斷當前是否有讀者讀文件:當有讀者時,寫者不能寫文件,此時讀者一直占用文件直到退出,寫者才可以寫文件;同時,不同讀者對計數器的訪問也是互斥的。
-
信號量設置
設置 count 信號量為計數器,初值為 0;
mutex 為互斥信號量,用於保護更新count 變量時的互斥;
互斥信號量 rw 用於保證讀者和寫者互斥訪問。
-
-
進程描述(讀進程優先)
int count=0; //記錄讀者數量 semaphore mutex=1; //更新count時互斥 semaphore rw=1; //讀者寫者互斥(文件資源) writer(){ while(1){ P(rw); // 互斥訪問共享文件 writing V(rw); // 釋放共享文件 } } reader(){ while(1){ P(mutex); // 互斥訪問 count 變量 if(count==0) // 當第一個讀進程讀共享文件時 P(rw) // 阻止寫進程 count++; V(mutex); // 釋放互斥變量 count reading; P(mutex); count--; if(count==0) // 當最后一個讀進程讀完共享文件 V(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); P(mutex); // 互斥訪問 count 變量 if(count==0) // 當第一個讀進程讀共享文件時 P(rw) // 阻止寫進程 count++; V(mutex); // 釋放互斥變量 count V(w); reading; P(mutex); count--; if(count==0) // 當最后一個讀進程讀完共享文件 V(rw); // 允許寫進程寫 V(mutex); } }
哲學家進餐問題
-
問題描述
一張圓桌上坐着5名哲學家,每兩名哲學家之間的桌子上擺着一根筷子,兩根筷子之間是一碗米飯。哲學家傾注畢生精力於思考和進餐,哲學家思考時不影響其他人。只有當哲學家飢餓時,才試圖拿起左、右兩根筷子——一根一根地拿起。若筷子已在他人手上,則需要等待。飢餓地哲學家只有同時拿到了兩根筷子才能開始進餐,進餐完畢,放下筷子繼續思考。
-
問題分析
-
關系分析
5 名哲學家與左右鄰座對其中間的筷子的訪問時互斥關系。
-
思路
5 個哲學家對應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); } //當五個人依次P(chopstick[i])時會導致死鎖
解決死鎖
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,offer2,offer3 分別表示煙草和紙組合的資源、煙草和膠水組合的資源、紙和膠水組合的資源;
信號量 finish 用於互斥進行抽煙動作。
-
-
進程描述
int count=0; semaphore offer1=0; semaphore offer2=0; semaphore offer3=0; semaphore finish=0; process P1(){ while(1){ count++; count=count%3; if(count==0) V(offer1); // 提供煙草和紙 else if(count==1) V(offer2); // 提供煙草和膠水 else V(offer3); //提供紙和膠水 put on ; // 將材料放在桌子上 P(finish); } } process P2(){ while(1){ P(offer3); working; // 拿起紙和膠水,卷成煙,抽掉 V(finish); } } process P3(){ while(1){ P(offer2); working; // 拿起煙草和膠水,卷成煙,抽掉 V(finish); } } process P4(){ while(1){ P(offer1); working; // 拿起紙和煙草,卷成煙,抽掉 V(finish); } }