互斥鎖與條件變量的配合!

互斥操作:
  對共享資源的訪問, 要對互斥量進行加鎖, 如果互斥量已經上了鎖, 調用線程會阻塞, 直到互斥量被解鎖. 在完成了對共享資源的訪問後, 要對互斥量進行解鎖。
    死鎖主要發生在有多個依賴鎖存在時, 會在一個線程試圖以與另一個線程相反順序鎖住互斥量時發生. 如何避免死鎖是使用互斥量應該格外注意的東西。

  總體來講, 有幾個不成文的基本原則:

  對共享資源操作前一定要獲得鎖。

  完成操作以後一定要釋放鎖。

  儘量短時間地佔用鎖。

  如果有多鎖, 如獲得順序是ABC連環扣, 釋放順序也應該是ABC。

  線程錯誤返回時應該釋放它所獲得的鎖。


條件變量是利用線程間共享的全局變量進行同步的一種機制,主要包括兩個動作:個線程等待"條件變量的條件成立"而掛起;另一個線程使"條件成立"(給出條件成立信號)。爲了防止競爭,條件變量的使用總是和一個互斥鎖結合在一起。

爲了能夠有效的控制多個進程之間的溝通過程,保證溝通過程的有序和和諧,OS必須提供一定的同步機制保證進程之間不會自說自話而是有效的協同工作。比如在 共享內存的通信方式中,兩個或者多個進程都要對共享的內存進行數據寫入,那麼怎麼才能保證一個進程在寫入的過程中不被其它的進程打斷,保證數據的完整性 呢?又怎麼保證讀取進程在讀取數據的過程中數據不會變動,保證讀取出的數據是完整有效的呢?

     常用的同步方式有: 互斥鎖、條件變量、讀寫鎖、記錄鎖(文件鎖)和信號燈.

互斥鎖:

     顧名思義,鎖是用來鎖住某種東西的,鎖住之後只有有鑰匙的人才能對鎖住的東西擁有控制權(把鎖砸了,把東西偷走的小偷不在我們的討論範圍了)。所謂互斥, 從字面上理解就是互相排斥。因此互斥鎖從字面上理解就是一點進程擁有了這個鎖,它將排斥其它所有的進程訪問被鎖住的東西,其它的進程如果需要鎖就只能等待,等待擁有鎖的進程把鎖打開後才能繼續運行。 在實現中,鎖並不是與某個具體的變量進行關聯,它本身是一個獨立的對象。進(線)程在有需要的時候獲得此對象,用完不需要時就釋放掉。

     互斥鎖的主要特點是互斥鎖的釋放必須由上鎖的進(線)程釋放,如果擁有鎖的進(線)程不釋放,那麼其它的進(線)程永遠也沒有機會獲得所需要的互斥鎖。

     互斥鎖主要用於線程之間的同步。

條件變量:

     上文中提到,對於互斥鎖而言,如果擁有鎖的進(線)程不釋放鎖,其它進(線)程永遠沒機會獲得鎖,也就永遠沒有機會繼續執行後續的邏輯。在實際環境下,一 個線程A需要改變一個共享變量X的值,爲了保證在修改的過程中X不會被其它的線程修改,線程A必須首先獲得對X的鎖。現在假如A已經獲得鎖了,由於業務邏 輯的需要,只有當X的值小於0時,線程A才能執行後續的邏輯,於是線程A必須把互斥鎖釋放掉,然後繼續“忙等”。如下面的僞代碼所示:

1.// get x lock

2.while(x <= 0){

3. // unlock x ;

4. // wait some time

5. // get x lock

6.}

7.// unlock x

     這種方式是比較消耗系統的資源的,因爲進程必須不停的主動獲得鎖、檢查X條件、釋放鎖、再獲得鎖、再檢查、再釋放,一直到滿足運行的條件的時候纔可以(而此過程中其他線程一直在等待該線程的結束)。因此我們需要另外一種不同的同步方式,當線程X發現被鎖定的變量不滿足條件時會自動的釋放鎖並把自身置於等待狀態,讓出CPU的控制權給其它線程。其它線程 此時就有機會去修改X的值,當修改完成後再通知那些由於條件不滿足而陷入等待狀態的線程。這是一種通知模型的同步方式,大大的節省了CPU的計算資源,減 少了線程之間的競爭,而且提高了線程之間的系統工作的效率。這種同步方式就是條件變量。 坦率的說,從字面意思上來將,“條件變量”這四個字是不太容易理解的。我們可以把“條件變量”看做是一個對象,一個會響的鈴鐺。當一個線程在獲 得互斥鎖之後,由於被鎖定的變量不滿足繼續運行的條件時,該線程就釋放互斥鎖並把自己掛到這個“鈴鐺”上。其它的線程在修改完變量後, 就搖搖“鈴鐺”, 告訴那些掛着的線程:“你們等待的東西已經變化了,都醒醒看看現在的它是否滿足你們的要求。”於是那些掛着的線程就知道自己醒來看自己是否能繼續跑下去 了。 

同樣換一種方式解釋:


互斥鎖,我要對一塊共享數據操作,但是我怕同時你也操作,那就亂套了,所以我要加鎖,這個時候我就開始操作這塊共享數據,而你進不了臨界區,等我操作完了,把鎖丟掉,你就可以拿到鎖進去操作了。條件變量,我要看一塊共享數據裏某一個條件是否達成,我很關心這個,如果我用互斥鎖,不停的進入臨界區看條件是否達成,這簡直太悲劇了,這樣一來, 我醒的時候會佔CPU資源,但是卻幹不了什麼時,只是頻繁的看條件是否達成,而且這對別人來說也是一種損失,我每次加上鎖,別人就進不了臨界區幹不了事 了。好吧,輪詢總是痛苦的,咱等別人通知吧,於是條件變量出現了,我依舊要拿個鎖,進了臨界區,看到了共享數據,發現,咦,條件還不到,於是我就調用 pthread_cond_wait(),先把鎖丟了,好讓別人可以去對共享數據做操作,然後呢?然後我就睡了,直到特定的條件發生,別人修改完了共享數 據,給我發了個消息,我又重新拿到了鎖,繼續幹俺要乾的事情了……

 

讀寫鎖:

     互斥鎖是排他性鎖,條件變量出現後和互斥鎖配合工作能夠有效的節省系統資源並提高線程之間的協同工作效率。互斥鎖的目的是爲了獨佔,條件變量的目的是爲了 等待和通知。但是現實世界是很複雜di,我們要解決的問題也是多種多樣di.從功能上來說,互斥鎖和條件變量能夠解決基本上所有的問題,但是性能上就不一 定完全滿足了。人的無休止的慾望促使人發明出針對性更強、性能更好的同步機制來。讀寫鎖就是這麼一個玩意兒。 考慮一個文件有多個進程要讀取其中的內容,但只有1個進程有寫的需求。我們知道讀文件的內容不會改變文件的內容,這樣即使多個進程同時讀相同的文件也沒什 麼問題,大家都能和諧共存。當寫進程需要寫數據時,爲了保證數據的一致性,所有讀的進程就都不能讀數據了,否則很可能出現讀出去的數據一半是舊的,一半是 新的狀況,邏輯就亂掉了。 爲了防止讀數據的時候被寫入新的數據,讀進程必須對文件加上鎖。現在假如我們有2個進程都同時讀,如果我們使用上面的互斥鎖和條件變量,當其中一個進程在 讀取數據的時候,另一個進程只能等待,因爲它得不到鎖。從性能上考慮,等待進程所花費的時間是完全的浪費,因爲這個進程完全可以讀文件內容而不會影響第一 個,但是這個進程沒有鎖,所以它什麼也做不了,只能等,等到花兒都謝了。 所以呢,我們需要一種其它類型的同步方式來滿足上面的需求,這就是讀寫鎖。 讀寫鎖的出現能夠有效的解決多進程並行讀的問題。每一個需要讀取的進程都申請讀鎖,這樣大家互不干擾。當有進程需要寫如數據時,首先申請寫鎖。如果在申請時發現有讀(或者寫)鎖存在,則該寫進程必須等待,一直等到所有的讀(寫)鎖完全釋放爲止。讀進程在讀取之前首先申請讀鎖,如果所讀數據被寫鎖鎖定,則該 讀進程也必須等待寫鎖被釋放。 很自然的,多個讀鎖是可以共存的,但寫鎖是完全互相排斥的。

記錄鎖(文件鎖):

     爲了增加並行性,我們可以在讀寫鎖的基礎上進一步細分被鎖對象的粒度。比如一個文件中,讀進程可能需要讀取該文件的前1k個字節,寫進程需要寫該文件的最 後1k個字節。我們可以對前1k個字節上讀鎖,對最後1k個自己上寫鎖,這樣兩個進程就可併發工作了。記錄鎖中的所謂“記錄”其實是“內容”的概念。使用 讀寫鎖可以鎖定一部分,而不是整個文件 。 文件鎖可以認爲是記錄鎖的一個特例,當使用記錄鎖鎖定文件的所有內容時,此時的記錄鎖就可以稱爲文件鎖了。

信號燈:

     信號燈可以說是條件變量的升級版。條件變量相當於鈴鐺,鈴鐺響後每個掛起的進程還需要自己獲得互斥鎖並判斷所需條件是否滿足,信號燈把這兩步操作糅合到一 起。 在Posix.1基本原理一文聲稱,有了互斥鎖和條件變量還提供信號燈的原因是:“本標準提供信號燈的而主要目的是提供一種進程間同步的方式 ;這些進程可能共享也可能不共享 內存區。互斥鎖和條件變量是作爲線程間的同步機制說明的 ;這些線程總是 共享(某個)內存區。這兩者都是已廣泛使用了多年的同步方式。每組原語都特別適合於特定的問題”。儘管信號燈的意圖在於進程間同步,互斥鎖和條件變量的意圖在於線程間同步,但是信號燈也可用於線程間,互斥鎖和條件變量 也可用於進程間。應當根據實際的情況進行決定。 信號燈最有用的場景是用以指明可用資源的數量。比如含有10個元素的數組,我們可以創建一個信號燈,初始值爲0.每當有進程需要讀數組中元素時(假設每次 僅能讀取1個元素),就申請使用該信號燈(信號燈的值減1),當有進程需要寫元素時,就申請掛出該信號等(信號燈值加1)。這樣信號燈起到了確定可用資源數量的作用。如果我們限定信號燈的值只能取0和1,就和互斥鎖的含義很相同了

發佈了62 篇原創文章 · 獲贊 6 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章