互斥量,生產者消費者

2021-08-19 14:29:28 字數 4346 閱讀 7148

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

互斥量是乙個可以處於兩態之一的變數:解鎖和加鎖。這樣,只需要乙個二進位制位表示它,不過實際上,常常使用乙個整型量,0表示解鎖,而其他所有的值則表示加鎖。互斥量使用兩個過程。當乙個執行緒(或程序)需要訪問臨界區時,它呼叫mutex_lock。如果該互斥量當前是解鎖的(即臨界區可用),此呼叫成功,呼叫執行緒可以自由進入該臨界區。

另一方面,如果該互斥量已經加鎖,呼叫執行緒被阻塞,直到在臨界區中的執行緒完成並呼叫mutex_unlock。如果多個執行緒被阻塞在該互斥量上,將隨機選擇乙個執行緒並允許它獲得鎖。

由於互斥量非常簡單,所以如果有可用的tsl或xchg指令,就可以很容易地在使用者空間中實現它們。用於使用者級執行緒包的mutex_lock和mutex_unlock**如圖2-29所示。xchg解法本質上是相同的。

圖2-29   mutex_lock和mutex_unlock的實現

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_initpthread_mutex_destroy。也可以通過pthread_mutex_lock給互斥量加鎖,如果該互斥量已被加鎖時,則會阻塞呼叫者。還有乙個呼叫可以用來嘗試鎖住乙個互斥量,當互斥量已被加鎖時會返回錯誤**而不是阻塞呼叫者。這個呼叫就是pthread_mutex_trylock。如果需要的話,該呼叫允許乙個執行緒有效地忙等待。最後,pthread_mutex_unlock用來給乙個互斥量解鎖,並在乙個或多個執行緒等待它的情況下正確地釋放乙個執行緒。互斥量也可以有屬性,但是這些屬性只在某些特殊的場合下使用。

除互斥量之外,

pthread提供了另一種同步機制:條件變數。互斥量在允許或阻塞對臨界區的訪問上是很有用的,條件變數則

允許執行緒由於一些未達到的條件而阻塞

。絕大部分情況下這兩種方法是一起使用的。

考慮一下生產者-消費者問題:乙個執行緒將產品放在乙個緩衝區內,由另乙個執行緒將它們取出。如果生產者發現緩衝區中沒有空槽可以使用了,它不得不阻塞起來直到有乙個空槽可以使用。生產者使用互斥量可以進行原子性檢查,而不受其他執行緒干擾。但是當發現緩衝區已經滿了以後,生產者需要一種方法來阻塞自己並在以後被喚醒。這便是條件變數做的事了。

與條件變數相關的pthread呼叫如圖2-31所示。有專門的呼叫用來建立和撤銷條件變數。它們可以有屬性,並且有不同的呼叫來管理它們(圖中沒有顯示)。與條件變數相關的最重要的兩個操作是pthread_cond_waitpthread_cond_signal。前者阻塞呼叫執行緒直到另一其他執行緒向它發訊號(使用後乙個呼叫)。當然,阻塞與等待的原因不是等待與發訊號協議的一部分。被阻塞的執行緒經常是在等待發訊號的執行緒去做某些工作、釋放某些資源或是進行其他的一些活動。只有完成後被阻塞的執行緒才可以繼續執行。條件變數允許這種等待與阻塞原子性地進行。當有多個執行緒被阻塞並等待同乙個訊號時,可以使用pthread_cond_broadcast呼叫。

條件變數與互斥量經常一起使用。這種模式用於讓乙個執行緒鎖住乙個互斥量,然後當它不能獲得它期待的結果時等待乙個條件變數

。最後另乙個執行緒會向它發訊號,使它可以繼續執行

。pthread_cond_wait原子性地呼叫並解鎖它持有的互斥量

。由於這個原因,互斥量是引數之一。

條件變數(不像訊號量)不會存在記憶體中

。如果將乙個訊號量傳遞給乙個沒有執行緒在等待的條件變數,那麼這個訊號就會丟失

。程式設計師必須小心使用避免丟失訊號。

圖2-32展示了乙個非常簡單只有乙個緩衝區的生產者-消費者問題。當生產者填滿緩衝區時,它在生產下乙個資料項之前必須等待,直到消費者清空了它。類似地,當消費者移走乙個資料項時,它必須等待,直到生產者生產了另外乙個資料項。使乙個執行緒睡眠的語句應該總是要檢查這個條件,以保證執行緒在繼續執行前滿足條件,因為執行緒可能已經因為乙個unix訊號或其他原因而被喚醒。

圖2-32   利用執行緒解決生產者-消費者問題

生產者消費者 生產者與消費者模式

一 什麼是生產者與消費者模式 其實生產者與消費者模式就是乙個多執行緒併發協作的模式,在這個模式中呢,一部分執行緒被用於去生產資料,另一部分執行緒去處理資料,於是便有了形象的生產者與消費者了。而為了更好的優化生產者與消費者的關係,便設立乙個緩衝區,也就相當於乙個資料倉儲,當生產者生產資料時鎖住倉庫,不...

生產者消費者

using system using system.collections.generic using system.threading namespace gmservice foreach thread thread in producers q.exit console.read public...

生產者消費者

執行緒通訊 乙個執行緒完成了自己的任務時,要通知另外乙個執行緒去完成另外乙個任務.wait 等待 如果執行緒執行了wait方法,那麼該執行緒會進入等待的狀態,等待狀態下的執行緒必須要被其他執行緒呼叫notify方法才能喚醒。notify 喚醒 喚醒執行緒池等待執行緒其中的乙個。notifyall 喚...