程序喚醒與睡眠

2021-10-05 21:47:30 字數 3716 閱讀 1767

使用睡眠與喚醒避免忙等待

在前一節如何避免多程序(執行緒)因競爭條件引發的錯誤?,我們提出了集中能保證多個程序互斥訪問臨界區,我們所提出的解決方案均使用忙等待策略,即在程序等待進入臨界區時,其持續檢查,直到能夠進入臨界區為止.我們能否在程序未滿足下一步工作條件時進入休眠狀態,當程序滿足下一步工作條件時,由其他程序喚醒該程序呢?如果可以實現這種方法,則我們可以避免程序的忙等待,從而節省cpu資源.

作業系統提供的sleep系統呼叫可以使當前程序進入休眠狀態,此時程序將被阻塞,進入阻塞狀態,cpu的佔有權被交出.如果當前某一程序達到喚醒條件,則可以執行wakeup(pid)喚醒編號為pid的程序.

使用如下生產者-消費者問題解釋程序的睡眠與喚醒.為簡化問題,我們假定只有乙個生產者,乙個消費者.如果當前用於存放待消費物品的空間有限,假定其一次最多存放n個物品.因此,如果當前空間滿,則生產者應當停止生產,進入睡眠狀態.當空間不滿時,再喚醒生產者.如果當前空間為空,則消費者程序應當停止消費,進入休眠狀態,當空間不為空時,再喚醒消費者程序.我們可以用如下偽**實現這一演算法:

int n =

100;

int count =0;

void

producer()

void

consumer()

以上**初看起來沒有問題,當注意到生產者程序與消費者程序具有共享記憶體區,且對共享記憶體區變數的訪問並未進行有效保護.在一般情況下,兩個程序同時訪問共享記憶體區似乎不會有太大問題,例如假設此時count == 3, 此時如果消費者程序被執行,則count變為2,假設此時還未進行實際消費,由於時鐘中斷,程序被切換到生成者程序,此時生產者新增生產item, count變為3,隨後程序切換回消費者,完成實際的消費.注意到,雖然count變數的增加與實際物品的生產與消費被劃分為兩個階段,但並未造成不良後果.

但如果是更為特殊的情形,則可能就會有麻煩了.假設此時count==1, 消費者程序首先執行,假設消費者程序消費完後,count = 0, 消費者程式再次呼叫判斷count == 0,此時發生時鐘中斷,由於消費者程序被中斷,因此並未執行sleep()這條語句,即消費者程序還未進入休眠狀態.此時切換到執行生產者程序.生產者完成生產後,count變數變為1,生產者隨後執行wakeup(counsumer)系統呼叫喚醒消費者程序.然而,由於此時消費者程序並未進入休眠狀態,因此當前訊號將被忽略.等到再次切換會消費者程序時,消費者程序執行sleep()進入休眠狀態,但該程序永遠不會被喚醒了.因為count變數此後會一直增加到n, 永遠不會再次等於1了.當count等於n時,生產者程序也進入睡眠狀態.由此,兩個程序均進入睡眠狀態,無法再次喚醒.

問題出在哪了?問題在於喚醒消費者程序的訊號傳送的太早了,此時消費者程序並未進入睡眠狀態,因此該訊號被忽略了.如果我們在接收到喚醒訊號時儲存起來,當消費者程序準備進入休眠狀態時,我們檢查是否存在喚醒訊號,如果有,則不進入休眠,並將當前喚醒訊號清楚.否則,進入休眠狀態.如果我們有多個程序,那我們就需要為每一程序儲存可能接受到的喚醒訊號,有沒有其他更合適的方法呢?

訊號量訊號量是e.w.dijkstra在2023年提出的一種方法,它使用乙個整型變數來累計喚醒次數,供以後使用。在他的建議中引入了乙個新的變數型別,稱作訊號量(semaphore)。乙個訊號量的取值可以為0(表示沒有儲存下來的喚醒操作)或者為正值(表示有乙個或多個喚醒操作)。

dijkstra建議設立兩種操作:down和up(分別為一般化後的sleep和wakeup)。對一訊號量執行down操作,則是檢查其值是否大於0。若該值大於0,則將其值減1(即用掉乙個儲存的喚醒訊號)並繼續;若該值為0,則程序將睡眠,而且此時down操作並未結束。檢查數值、修改變數值以及可能發生的睡眠操作均作為乙個單一的、不可分割的原子操作完成。保證一旦乙個訊號量操作開始,則在該操作完成或阻塞之前,其他程序均不允許訪問該訊號量。這種原子性對於解決同步問題和避免競爭條件是絕對必要的。所謂原子操作,是指一組相關聯的操作要麼都不間斷地執行,要麼都不執行。原子操作在電腦科學的其他領域也是非常重要的。

確保訊號量能正確工作,最重要的是要採用一種不可分割的方式來實現它。通常是將up和down作為系統呼叫實現,而且作業系統只需在執行以下操作時暫時遮蔽全部中斷:測試訊號量、更新訊號量以及在需要時使某個程序睡眠。由於這些動作只需要幾條指令,所以遮蔽中斷不會帶來什麼***。如果使用多個cpu,則每個訊號量應由乙個鎖變數進行保護。通過tsl或xchg指令來確保同一時刻只有乙個cpu在對訊號量進行操作。

up操作對訊號量的值增1。如果乙個或多個程序在該訊號量上睡眠(在訊號量為0時呼叫了down操作),無法完成乙個先前的down操作,則由系統選擇其中的乙個(如隨機挑選)並允許該程序完成它的down操作。於是,對乙個有程序在其上睡眠的訊號量執行一次up操作之後,該訊號量的值仍舊是0,但在其上睡眠的程序卻少了乙個。訊號量的值增1和喚醒乙個程序同樣也是不可分割的。不會有某個程序因執行up而阻塞,正如在前面的模型中不會有程序因執行wakeup而阻塞一樣。

注意到,多個程序可以共用乙個訊號量.例如,在生產者-消費者問題中,假設存在多個生產者與多個消費者.則我們可以僅使用乙個訊號量來紀錄對消費者程序可能的睡眠與喚醒訊號.假設當前訊號量為0,此時如果3個消費者程序先後嘗試執行,則其無法成功執行down操作,當前訊號量上有三個程序進入睡眠狀態.此後,如果生產者程序被呼叫,則其在訊號量上執行up操作,由於3個消費者程序在該訊號量上被阻塞,因此隨機選擇其中乙個程序喚醒.此時該訊號量上的睡眠程序數變為2.為了表示生產者程序的睡眠與喚醒訊號,我們需要使用另乙個訊號量.每次呼叫消費者程序時,在該訊號量上執行up操作,每次呼叫生產者程序時,在該訊號量上執行down操作.

基於訊號量的生產者-消費者多程序解決方案

該解決方案使用了三個訊號量:乙個稱為full,用來記錄充滿的緩衝槽數目;乙個稱為empty,記錄空的緩衝槽總數;乙個稱為mutex,用來確保生產者和消費者不會同時訪問緩衝區。full的初值為0,empty的初值為緩衝區中槽的數目,mutex初值為1。供兩個或多個程序使用的訊號量,其初值為1,保證同時只有乙個程序可以進入臨界區,稱作二元訊號量(binary semaphore)。如果每個程序在進入臨界區前都執行乙個down操作,並在剛剛退出時執行乙個up操作,就能夠實現互斥。

#define n 100

typedef

int semaphore

semaphore mutex 1

;semaphore full 0

;semaphore empty n;

void

producer()

void

consume()

訊號量的另一種用途是用於實現同步(synchronization)。訊號量full和empty用來保證某種事件的順序發生或不發生。在本例中,它們保證當緩衝區滿的時候生產者停止執行,以及當緩衝區空的時候消費者停止執行。這種用法與互斥是不同的。而訊號量mutex在本例中的作用則是保證互斥.

注意觀察上面使用訊號量的程式,如果我們在生產者程序中,將down(&mutex)放在down(&empty)前面執行可能會造成什麼問題?

假設當前緩衝區已滿,此時如果生產者程序執行down(&mutex)成功,則在執行down(&empty)時,會被阻塞.因此,其無法執行up(&mutex).這導致消費者程序無法執行down(&mutex).因此消費者程序無法消耗緩衝區中的內容.此時兩個程序均進入阻塞狀態,即死鎖.

linux0 11程序睡眠喚醒原理分析

程序的睡眠是通過呼叫sleep on函式,該函式修改了程序的狀態並且通過schedule函式切換到其他程序執行,從而實現程序的掛起,task uninterruptible狀態的程序只能被wake up函式喚醒。task interruptible狀態的程序可以被wake up和訊號喚醒。喚醒的時候...

Linux程序的睡眠和喚醒簡析

1 linux程序的睡眠和喚醒 在linux中,僅等待cpu時間的程序稱為就緒程序,它們被放置在乙個執行佇列中,乙個就緒程序的狀 態標誌位為task running。一旦乙個執行中的程序時間片用完,linux 核心的排程器會剝奪這個程序對cpu的控制權,並且從執行佇列中選擇乙個合適的程序投入執行。當...

1 睡眠喚醒 電腦休眠黑屏怎麼喚醒?

鍵盤任意鍵或移動滑鼠這些方法可以用於喚醒處於睡眠狀態的電腦 如果按鍵盤 移動滑鼠都沒反應,那麼你的電腦有可能進入了休眠狀態,這個時候需要按下電源按鈕以此來喚醒電腦。以下是詳細介紹 1 用按鍵盤任意鍵或移動滑鼠這些方法是用於喚醒處於睡眠狀態的電腦,一般就可以將電腦喚醒到桌面 2 如果按鍵盤 移動滑鼠都...