互斥量

互斥量

互斥量又稱互斥鎖。互斥量是一個可以處於兩態之一的變數:解鎖和加鎖。

簡介


如果不需要信號量的計數能力,有時可以使用信號量的一個簡化版本,稱為互斥量(mutex)。互斥量僅僅適用於管理共享資源或一小段代碼。由於互斥量在實現時既容易又有效,這使得互斥量在實現用戶空間線程包時非常有用。

特徵


互斥量是一個可以處於兩態之一的變數:解鎖和加鎖。這樣,只需要一個二進位位表示它,不過實際上,常常使用一個整型量,0表示解鎖,而其他所有的值則表示加鎖。互斥量使用兩個過程。當一個線程(或進程)需要訪問臨界區時,它調用mutex_lock。如果該互斥量當前是解鎖的(即臨界區可用),此調用成功,調用線程可以自由進入該臨界區。
另一方面,如果該互斥量已經加鎖,調用線程被阻塞,直到在臨界區中的線程完成並調用mutex_unlock。如果多個線程被阻塞在該互斥量上,將隨機選擇一個線程並允許它獲得鎖。
由於互斥量非常簡單,所以如果有可用的TSL或XCHG指令,就可以很容易地在用戶空間中實現它們。用於用戶級線程包的mutex_lock和mutex_unlock代碼如圖2-29所示。XCHG解法本質上是相同的。
互斥量
互斥量
mutex_lock 的代碼與圖2-25中enter_region的代碼很相似,但有一個關鍵的區別。當enter_region進入臨界區失敗時,它始終重複測試鎖(忙等待)。實際上,由於時鐘超時的作用,會調度其他進程運行。這樣遲早擁有鎖的進程會進入運行並釋放鎖。
在(用戶)線程中,情形有所不同,因為沒有時鐘停止運行時間過長的線程。結果是通過忙等待的方式來試圖獲得鎖的線程將永遠循環下去,決不會得到鎖,因為這個運行的線程不會讓其他線程運行從而釋放鎖。
以上就是enter_region和mutex_lock 的差別所在。在後者取鎖失敗時,它調用thread_yield將CPU放棄給另一個線程。這樣,就沒有忙等待。在該線程下次運行時,它再一次對鎖進行測試。
由於thread_yield只是在用戶空間中對線程調度程序的一個調用,所以它的運行非常快捷。這樣,mutex_lock和mutex_unlock都不需要任何內核調用。通過使用這些過程,用戶線程完全可以實現在用戶空間中的同步,這些過程僅僅需要少量的指令。
上面所敘述的互斥量系統是一套調用框架。對於軟體來說,總是需要更多的特性,而同步原語也不例外。例如,有時線程包提供一個調用mutex_trylock,這個調用或者獲得鎖或者返回失敗碼,但並不阻塞線程。這就給了調用線程一個靈活性,用以決定下一步做什麼,是使用替代辦法還只是等待下去。
到目前為止,我們掩蓋了一個問題,不過現在還是有必要把這個問題提出來。在用戶級線程包中,多個線程訪問同一個互斥量是沒有問題的,因為所有的線程都在一個公共地址空間中操作。但是,對於大多數早期解決方案,諸如Peterson演演算法和信號量等,都有一個未說明的前提,即這些多個進程至少應該訪問一些共享內存,也許僅僅是一個字。如果進程有不連續的地址空間,如我們始終提到的,那麼在Peterson演演算法、信號量或公共緩衝區中,它們如何共享turn變數呢?
有兩種方案。第一種,有些共享數據結構,如信號量,可以存放在內核中,並且只能通過系統調用來訪問。這種處理方式化解了上述問題。第二種,多數現代操作系統(包括UNIX和Windows)提供一種方法,讓進程與其他進程共享其部分地址空間。在這種方法中,緩衝區和其他數據結構可以共享。在最壞的情形下,如果沒有可共享的途徑,則可以使用共享文件。
如果兩個或多個進程共享其全部或大部分地址空間,進程和線程之間的差別就變得模糊起來,但無論怎樣,兩者的差別還是有的。共享一個公共地址空間的兩個進程仍舊有各自的打開文件、報警定時器以及其他一些單個進程的特性,而在單個進程中的線程,則共享進程全部的特性。另外,共享一個公共地址空間的多個進程決不會擁有用戶級線程的效率,這一點是不容置疑的,因為內核還同其管理密切相關。
Pthread中的互斥
Pthread提供許多可以用來同步線程的函數。其基本機制是使用一個可以被鎖定和解鎖的互斥量來保護每個臨界區。一個線程如果想要進入臨界區,它首先嘗試鎖住相關的互斥量。如果互斥量沒有加鎖,那麼這個線程可以立即進入,並且該互斥量被自動鎖定以防止其他線程進入。如果互斥量已經被加鎖,則調用線程被阻塞,直到該互斥量被解鎖。如果多個線程在等待同一個互斥量,當它被解鎖時,這些等待的線程中只有一個被允許運行並將互斥量重新鎖定。這些互斥鎖不是強制性的,而是由程序員來保證線程正確地使用它們。
與互斥量相關的主要函數調用如圖2-30所示。就像所期待的那樣,可以創建和撤銷互斥量。實現它們的函數調用分別是pthread_mutex_init與pthread_mutex_destroy。也可以通過pthread_mutex_lock給互斥量加鎖,如果該互斥量已被加鎖時,則會阻塞調用者。還有一個調用可以用來嘗試鎖住一個互斥量,當互斥量已被加鎖時會返回錯誤代碼而不是阻塞調用者。這個調用就是pthread_mutex_trylock。如果需要的話,該調用允許一個線程有效地忙等待。最後,pthread_mutex_unlock用來給一個互斥量解鎖,並在一個或多個線程等待它的情況下正確地釋放一個線程。互斥量也可以有屬性,但是這些屬性只在某些特殊的場合下使用。
互斥量
互斥量
除互斥量之外,pthread提供了另一種同步機制:條件變數。互斥量在允許或阻塞對臨界區的訪問上是很有用的,條件變數則允許線程由於一些未達到的條件而阻塞。絕大部分情況下這兩種方法是一起使用的。現在讓我們進一步地研究線程、互斥量、條件變數之間的關聯。
舉一個簡單的例子,再次考慮一下生產者-消費者問題:一個線程將產品放在一個緩衝區內,由另一個線程將它們取出。如果生產者發現緩衝區中沒有空槽可以使用了,它不得不阻塞起來直到有一個空槽可以使用。生產者使用互斥量可以進行原子性檢查,而不受其他線程干擾。但是當發現緩衝區已經滿了以後,生產者需要一種方法來阻塞自己並在以後被喚醒。這便是條件變數做的事了。
與條件變數相關的pthread調用如圖2-31所示。就像你可能期待的那樣,這裡有專門的調用用來創建和撤銷條件變數。它們可以有屬性,並且有不同的調用來管理它們(圖中沒有顯示)。與條件變數相關的最重要的兩個操作是pthread_cond_wait和pthread_cond_signal。前者阻塞調用線程直到另一其他線程向它發信號(使用后一個調用)。當然,阻塞與等待的原因不是等待與發信號協議的一部分。被阻塞的線程經常是在等待發信號的線程去做某些工作、釋放某些資源或是進行其他的一些活動。只有完成後被阻塞的線程才可以繼續運行。條件變數允許這種等待與阻塞原子性地進行。當有多個線程被阻塞並等待同一個信號時,可以使用pthread_cond_broadcast調用。
互斥量
互斥量
互斥量
互斥量