經典程序同步與互斥習題總結
基礎知識導引
臨界資源
在作業系統中,程序是佔有資源的最小單位(執行緒可以訪問其所在程序內的所有資源,但執行緒本身並不佔有資源或僅僅佔有一點必須資源)。但對於某些資源來說,其在同一時間只能被一個程序所佔用。這些一次只能被一個程序所佔用的資源就是所謂的臨界資源。
典型的臨界資源比如物理上的印表機,或是存在硬碟或記憶體中被多個程序所共享的一些變數和資料等(如果這類資源不被看成臨界資源加以保護,那麼很有可能造成丟資料的問題)。
對於臨界資源的訪問,必須是互訴進行。也就是當臨界資源被佔用時,另一個申請臨界資源的程序會被阻塞,直到其所申請的臨界資源被釋放。而程序內訪問臨界資源的程式碼被成為臨界區。
對於臨界區的訪問過程分為四個部分:
1.進入區:檢視臨界區是否可訪問,如果可以訪問,則轉到步驟二,否則程序會被阻塞
2.臨界區:在臨界區做操作
3.退出區:清除臨界區被佔用的標誌
4.剩餘區:程序與臨界區不相關部分的程式碼
互斥
程序互斥是程序之間的間接制約關係。當一個程序進入臨界區使用臨界資源時,另一個程序必須等待。只有當使用臨界資源的程序退出臨界區後,這個程序才會解除阻塞狀態。
比如程序B需要訪問印表機,但此時程序A佔有了印表機,程序B會被阻塞,直到程序A釋放了印表機資源,程序B才可以繼續執行。概念如圖所示。
同步
程序同步也是程序之間直接的制約關係,是為完成某種任務而建立的兩個或多個執行緒,這個執行緒需要在某些位置上協調他們的工作次序而等待、傳遞資訊所產生的制約關係。程序間的直接制約關係來源於他們之間的合作。
比如說程序A需要從緩衝區讀取程序B產生的資訊,當緩衝區為空時,程序B因為讀取不到資訊而被阻塞。而當程序A產生資訊放入緩衝區時,程序B才會被喚醒。概念如圖所示。
實現臨界區互斥的基本方法
硬體實現方法
1 關中斷。
2利用Test-and-Set指令實現互斥
3利用Swap指令實現程序互斥
訊號量實現方式
這也是我們比較熟悉P V操作。通過設定一個表示資源個數的訊號量S,通過對訊號量S的P和V操作來實現程序的的互斥。
P和V操作分別來自荷蘭語Passeren和Vrijgeven,分別表示佔有和釋放。P V操作是作業系統的原語,意味著具有原子性。
P操作首先減少訊號量,表示有一個程序將佔用或等待資源,然後檢測S是否小於0,如果小於0則阻塞,如果大於0則佔有資源進行執行。
V操作是和P操作相反的操作,首先增加訊號量,表示佔用或等待資源的程序減少了1個。然後檢測S是否小於0,如果小於0則喚醒等待使用S資源的其它程序。
經典題總結
下面將以大量的篇幅總結最近所學和接觸到的程序同步問題作出總結,難免有錯誤出現,還請包涵指出。
Q1:生產者和消費者
問題描述:一組生產者程序和消費者程序共享一個初始為空,大小為n的緩衝區。只有當緩衝區沒滿的時候,生產者才能將訊息放進去。同理,只有當緩衝區不空的時候,消費者才能從中取訊息,否則必須等待。由於緩衝區是臨界資源,它只允許一個生產者放入訊息,也只允許一個消費者拿出訊息。這裡我再解釋一下,意思是,同一個時刻只能是一個生產者或者一個消費者操作緩衝區,禁止一下情況:多個生產者或者多個消費者操作緩衝區,同樣,一個生產者和一個消費者同時操作也是禁止的。
分析:首先,生產者之間,消費者之間是互斥的關係,同時生產者消費者之間又是協同的關係,屬於程序同步。
其次,設定訊號量,我們知道訊號量個數等於資源數,我們用empty=n表示緩衝區空的緩衝區數目,用full=0表示緩衝區滿的緩衝區數目。同時,還要一個訊號量mutex來實現,諸程序對緩衝區的互斥訪問。
1利用記錄性訊號量解決生產者-消費者問題
1 int in=0,out=0; 2 item buffer[n]; 3 semaphore mutex=1,empty=n,full=0; 4 void proceducer(){ 5 do{ 6 producer an item nextp; 7 ... 8 wait(empty); 9 wait(mutex); 10 buffer[in]=nextp; 11 in=(in+1)%n; 12 signal(mutex); 13 signal(full); 14 }while(true); 15 } 16 void consumer(){ 17 do{ 18 wait(full); 19 wait(mutex); 20 nextc=buffer[out]; 21 out=(out+1)%n; 22 signal(mutex); 23 signal(empty); 24 consumer the item in nextc; 25 }while(true); 26 } 27 void main() 28 { 29 cobegin 30 proceducer(); consumer(); 31 coend; 32 }
注意8和9行及18和19行的次序,寫反會導致死鎖
2利用and訊號量解決生產者和消費者問題
通過Swait(empty,mutex)代替wait(empty)和wait(mutex).Signal(mutex,full)代替signal(mutex)和signal(full)
通過Swait(full,mutex)代替wait(full)和wait(mutex).Signal(mutex,empty)代替signal(mutex)和signal(empty)
1 int in=0,out=0; 2 item buffer[n]; 3 semaphore mutex=1,empty=n,full=0; 4 void proceducer(){ 5 do{ 6 producer an item nextp; 7 ... 8 Swait(empty,mutex); 9 buffer[in]=nextp; 10 in:=(in+1)%n; 11 Ssignal(mutex,full); 12 }while(true); 13 } 14 void consumer(){ 15 do{ 16 Swait(full,mutex); 17 nextc=buffer[out]; 18 out:=(out+1)%n; 19 Ssignal(mutex,empty); 20 consumer the item in nextc; 21 ... 22 }while(true); 23 } 24 void main() 25 { 26 cobegin 27 proceducer(); consumer(); 28 coend; 29 }
Q2哲學家進餐問題
問題描述:有五個哲學家,他們的生活方式是交替地進行思考和進餐。他們共用一張圓桌,分別坐在五張椅子上。
在圓桌上有五個碗和五支筷子,平時一個哲學家進行思考,飢餓時便試圖取用其左、右最靠近他的筷子,只有在他拿到兩支筷子時才能進餐。進餐完畢,放下筷子又繼續思考。
分析:筷子是臨界資源,在一段時間內只允許一位哲學家使用,為實現筷子的互斥使用,可以使用一個訊號量來表示一隻筷子,這五個訊號量構成訊號量陣列。
1 semaphore chopsticks[5]={1,1,1,1,1}; 2 do{ 3 wait(chopsticks[i]);\ 4 wait(chopsticks[i+1]%5); 5 ... 6 //eat 7 signal(chopsticks[i]); 8 signal(chopsticks[i+1]%5); 9 ... 10 //think 11 ... 12 }while(true)