linux進程間同步

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

  • 互斥鎖
  • 條件變量
  • 讀寫鎖
  • 記錄鎖(文件鎖)

互斥鎖

所謂互斥,從字面上理解就是互相排斥。因此互斥鎖從字面上理解就是一個進程擁有了這個鎖,它將排斥其它所有的進程訪問被鎖住的東西,其它的進程如果需要鎖就只能等待,等待擁有鎖的進程把鎖打開后才能繼續運行。互斥鎖的主要特點是互斥鎖的釋放必須由上鎖的進(線)程釋放,如果擁有鎖的進(線)程不釋放,那么其它的進(線)程永遠也沒有機會獲得所需要的互斥鎖。互斥鎖主要用于線程之間的同步

條件變量

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

// get x lock
while(x <= 0){
     // unlock x ;
     // wait some time 
    // get x lock
}
// unlock x

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

讀寫鎖

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

條件變量的另外一種解釋

假設這樣一個狀況: 有100個工人,每人負責裝修一個房間。當有10個房間裝修完成的時候,老板就通知相應的十個工人一起去喝啤酒。
我們如何實現呢?老板讓工人在裝修好房間之后,去檢查已經裝修好的房間數。但多線程條件下,會有競爭條件的危險。也就是說,其他工人有可能會在該工人裝修好房子和檢查之間完成工作。采用下面方式解決:

/*mu:  global mutex,
  cond: global codition variable,
  num: global int
*/
mutex_lock(mu)
num = num + 1; /*worker build the room*/
if (num <= 10) { /*worker is within the first 10 to finish*/    
   cond_wait(mu, cond);     /*wait*/
   printf("drink beer");
}else if (num = 11) { 
 /*workder is the 11th to finish*/
 cond_broadcast(mu, cond);        /*inform the other 9 to wake up*/}
mutex_unlock(mu);

上面使用了條件變量。條件變量除了要和互斥鎖配合之外,還需要和另一個全局變量配合(這里的num, 也就是裝修好的房間數)。這個全局變量用來構成各個條件。

具體思路如下。我們讓工人在裝修好房間(num = num + 1)之后,去檢查已經裝修好的房間數( num < 10 )。由于mu被鎖上,所以不會有其他工人在此期間裝修房間(改變num的值)。如果該工人是前十個完成的人,那么我們就調用cond_wait()函數。cond_wait()做兩件事情,一個是釋放mu,從而讓別的工人可以建房。另一個是等待,直到cond的通知。這樣的話,符合條件的線程就開始等待。
當有通知(第十個房間已經修建好)到達的時候,condwait()會再次鎖上mu。線程的恢復運行,執行下一句prinft("drink beer") (喝啤酒!)。從這里開始,直到mutex_unlock(),就構成了另一個互斥鎖結構。
那么,前面十個調用cond_wait()的線程如何得到的通知呢?我們注意到elif if,即修建好第11個房間的人,負責調用cond_broadcast()。這個函數會給所有調用cond_wait()的線程放送通知,以便讓那些線程恢復運行

條件變量特別適用于多個線程等待某個條件的發生。如果不使用條件變量,那么每個線程就需要不斷嘗試獲得互斥鎖并檢查條件是否發生,這樣大大浪費了系統的資源。

記錄鎖(文件鎖)

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

信號燈
一般意義下,信號燈是一個具有整數值的對象,它支持兩種操作P()和V()。P()操作減少信號燈的值,如果新的信號燈的值小于0,則操作阻塞;V()操作增加信號燈的值,如果結果值大于或等于0,則喚醒一個等待的進程。通常用信號燈來做進程的同步和互斥。
最簡單形式的信號燈就是內存中一個存儲位置,它的取值可以由多個進程檢驗和設置。至少對于相關的進程來講,對信號燈的檢驗和設置操作是不可中斷的或者說是原子的:只要啟動就不能終止。目前許多處理器提供檢驗和設置操作指令,如Intel處理器的sete等指令。檢驗和設置操作的結果是信號燈當前值與設置值的和,可以是正或者負。根據檢驗和設置操作的結果,一個進程可能必須睡眠直到信號燈的值被另一個進程改變。信號燈可以用于實現臨界區(critical regions),就是重要的代碼區,同一時刻只能有一個進程運行的代碼區域。
比如,有許多協作的進程要從同一個數據文件中讀寫記錄,并且希望對文件的訪問必須嚴格地協調。那么,可以使用一個信號燈,將其初值設為1,用兩個信號燈操作(P、V 操作),將進程中對文件操作的代碼括起來。第一個信號燈操作檢查并把信號燈的值減小,第二個操作檢查并增加它。訪問文件的第一個進程試圖減小信號燈的值,如果它成功(事實上,它肯定成功),信號燈的取值將變為0,這個進程現在可以繼續運行并使用數據文件。但是,如果此時另一個進程需要使用這個文件,它也試圖減少信號燈的數值,它會失敗,因為信號燈的值將要變成-1(但是,信號燈的值仍然保持為0,沒有變成-1),這個進程會被掛起直到第一個進程處理完數據文件。當第一個進程處理完數據文件后,它會增加信號燈的值使其重新變為1。現在等待的進程會被喚醒,這次它減小信號燈的嘗試會成功。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,836評論 6 540
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,275評論 3 428
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,904評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,633評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,368評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,736評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,740評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,919評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,481評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,235評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,427評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,968評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,656評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,055評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,348評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,160評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,380評論 2 379

推薦閱讀更多精彩內容