作業系統中的同步互斥(鎖與訊號量)

2022-07-12 08:15:08 字數 2679 閱讀 1110

作業系統的同步與互斥可以從執行緒和程序兩個角度進行理解。如果從執行緒的角度理解,這裡本文以兩個執行緒為例,需要考慮這兩個執行緒是否屬於同乙個程序,對於不同程序的執行緒來說,它們本質上和從兩個程序的角度進行理解是一樣的,在之後討論兩個程序間的同步互斥時會詳細說明。對於同一程序的兩個執行緒,假設有這樣一段**。

int res, temp=0;

res = temp++;

上文的**是通過c語言編寫的,需要經過編譯、鏈結之後才能執行,經過編譯後,「res=temp++;」可能被翻譯成如下的彙編指令。

load temp, reg1

store reg1, res

inc reg1

store temp, reg1

如果兩個執行緒同時執行這樣一段**,在執行過程中,可能發生執行緒切換,導致乙個執行緒沒有全部執行完這4條指令,就將執行許可權交到另乙個執行緒的情況。考慮這樣一種情況,執行緒1在執行完inc reg1之後發生執行緒切換,第二個執行緒開始執行,如果第二個執行緒正常執行完畢,將temp置為1,然後切回執行緒1,再次將temp置為1。其實這已經和我們的初衷不符,因為正常情況下,我們通常認為temp應該等於2,而且更重要的是,這個**帶有不確定性,如果兩個執行緒執行時,temp可能為1也可能為2,res的值也不確定。

一種簡單的做法是加鎖,還是看一段**。

int res,temp=0;

lock(p);

res = temp++;

unlock(p);

這裡假設p是乙個全域性變數,初始化為1,函式lock(p)可以理解為讀取p的值,如果p>0則p執行自減操作,如果p=0則將當前執行緒睡眠乙個固定的時間,然後再來查詢p的值,這個過程可以表示為如下**。

void lock(int p)

sleep(10);

}}

unlock的**同理,這裡不詳細寫了。看到這裡讀者可能會發現,這段**看似解決了以前的問題,但是帶來了兩個新的問題:

這段**並不能真正讓多執行緒正確工作,比如執行緒1執行時,假設p=1,那麼(p>0)是成立的,但是如果恰巧執行完p>0以後執行緒切換,執行緒1讓出執行許可權給執行緒2,那麼執行緒2在判斷p>0時也是成立的,這時兩個執行緒仍然同時進入到臨界區(我們把不允許多執行緒同時執行的區域稱為臨界區或互斥區,下同),因此不能解決上述問題。

第二個問題是,即使多個執行緒不會同時進入到臨界區,也會導致忙等待的問題。具體來說,如果執行緒1進入到臨界區,這時切換到執行緒2,執行緒2可能也執行這段**,當它試圖執行lock(p)時,它會一直輪詢p的狀態,此時執行緒1沒有執行,那麼它這個時間片(執行緒2的執行時間)事實上是浪費了,如果執行緒2的優先順序高於執行緒1,而且執行緒的排程演算法是優先順序高的執行緒總是先執行,那將產生可怕的後果,執行緒1永遠也不能執行,因此永遠也不會釋放鎖,而執行緒2永遠在輪詢,永遠在浪費時間片。

顯然,上述兩個問題是不能迴避的,這兩個問題必須得到解決。針對第乙個問題,事實上我們採用硬體提供的方法,由硬體確保查詢和更改操作是原子操作,簡單來說,就是判斷(p>0)和執行p--這兩個操作是原子操作,要麼都做要麼都不做,我記得c庫會提供乙個大致叫compareandchange的函式來完成這個操作。

針對第二個問題,要解決起來就複雜的多。首先,作業系統將執行緒分為三種狀態,分別是就緒(ready)、掛起(suspend)、執行(execute),事實上這三種狀態在很多地方都會用到,這裡只考慮在訪問臨界區時的應用。首先介紹一下這三種狀態,就緒態的執行緒是指乙個執行緒已經就緒,簡單來說就是可以被排程執行,需要注意,同一時刻可能存在多個就緒態的執行緒,如果當前執行的執行緒執行完畢後,會從當前多個就緒執行緒中選取乙個執行緒(一般選擇優先順序最高的)切換到執行態。執行態的執行緒在同一時刻只有乙個(事實上執行態的執行緒個數取決於cpu核的個數,但又不僅僅取決於cpu核的個數,這裡不詳細討論),掛起態比較特殊,這類執行緒往往是由於資源得不到滿足而掛起,等到資源滿足以後再被喚醒切換到就緒態。舉個簡單的掛起態的例子,比如乙個執行緒想要讀磁碟,那麼它只需要發乙個系統呼叫告訴核心,再由核心告訴磁碟讀取指定區域的資料,但是這個讀取是需要時間的,此時這個執行緒就被阻塞了,因此給它時間片也沒用,所以它會被os掛起,當磁碟讀取完成後,可以告訴核心,然後由核心再將上述掛起執行緒喚醒。

回到這個問題,當執行緒1執行了lock(p)之後進入到臨界區以後,如果這時執行緒1讓出執行許可權,由執行緒2開始執行,那麼當它執行到lock(p)時,它不會再去輪詢p到狀態,而是會將自己從執行態(因為此時執行緒2在執行,所以必然處於執行態)變為掛起狀態。需要注意的是,無論執行緒2的優先順序多麼高,此時執行緒2再也沒有執行的可能了。接下來,如果執行緒1執行完畢後,它會執行unlock(p),那麼此時unlock(p)也不能僅僅做p++了,它需要喚醒執行緒2,也就是喚醒等待p的執行緒。此時p已經不僅僅是乙個整數那麼簡單了,準備的說,p已經是乙個訊號量了,訊號量肯定比乙個整數要複雜很多,但從原理上講,也不需要很複雜。那麼乙個訊號量需要什麼呢?我想它應該需要兩樣東西:

乙個整數記錄當前訊號量的值,訊號量的值不總是1,比如臨界區的**是操作印表機,而此時存在十個印表機,那麼允許十個執行緒同時進入到臨界區,因此訊號量可以是10,當然大多數情況下訊號量只有0、1兩個取值。

如此,乙個簡單的訊號量就設計完成了,對於訊號量的操作,一般稱為p和v操作,p相當於lock、v相當於unlock。當然,現在的作業系統對於訊號量的設計遠沒有這麼簡單,考慮的情況也要複雜很多,這只是乙個簡單的分析,如果有讀者在這方面想要交流,歡迎發郵件給我。

當然,這裡考慮的仍然是非常簡單的情況,讀者可以考慮按照這種思路會出現哪些無法解決的問題??或者仍有哪些問題沒有考慮到??

執行緒互斥與同步 互斥鎖與訊號量

所謂互斥,就是不同執行緒通過競爭進入臨界區 共享的資料和硬體資源 為了防止訪問衝突,在有限的時間內只允許其中之一獨占性的使用共享資源。如不允許同時寫 同步關係則是多個執行緒彼此合作,通過一定的邏輯關係來共同完成乙個任務。一般來說,同步關係中往往包含互斥,同時對臨界區的資源會按照某種邏輯順序進行訪問。...

互斥量與訊號量(互斥與同步)

互斥量 mutex 互斥量表現互斥現象的資料結構,也被當作二元訊號燈。乙個互斥基本上是乙個多工敏感的二元訊號,它能用作同步多工的行為,它常用作保護從中斷來的臨界段 並且在共享同步使用的資源。mutex本質上說就是一把鎖,提供對資源的獨佔訪問,所以mutex主要的作用是用於互斥。mutex物件的值,只...

訊號量與互斥鎖

訊號量與普通整型變數的區別 訊號量 semaphore 是非負整型變數,除了初始化之外,它只能通過兩個標準原子操作 wait semap signal semap 來進行訪問 操作也被成為pv原語 p 於dutch proberen 測試 v 於 dutch verhogen 增加 而普通整型變數則...