並行線程

<condition_variable>是C++標準程序庫中的一個頭文件,定義了C++11標準中的一些用於併發編程時表示條件變量的類與方法等。

條件變量是併發程序設計中的一種控制結構。多個線程訪問一個共享資源(或稱臨界區)時,不但需要用互斥鎖實現獨享訪問以避免併發錯誤(稱爲競爭危害),在獲得互斥鎖進入臨界區後還需要檢驗特定條件是否成立:

(1) 如果不滿足該條件,擁有互斥鎖的線程應該釋放該互斥鎖,把自身阻塞(block)並掛到(suspend)條件變量的線程隊列中

(2) 如果滿足該條件,擁有互斥鎖的線程在臨界區內訪問共享資源,在退出臨界區時通知(notify)在條件變量的線程隊列中處於阻塞狀態的線程,被通知的線程必須重新申請對該互斥鎖加鎖

C++11的標準庫中新增加的條件變量的實現,與pthread的實現語義完全一致。使用條件變量做併發控制時,某一時刻阻塞在一個條件變量上的各個線程應該在調用wait操作時指明同一個互斥鎖,此時該條件變量與該互斥鎖綁定(??);否則程序的行爲未定義。條件變量必須與互斥鎖配合使用,其理由是程序需要判定某個條件(condition或稱predict)是否成立,該條件可以是任意複雜。

std::condition_variable類的成員函數:

(1) 構造函數:僅支持默認構造函數,拷貝、賦值和移動(move)均是被禁用的。

(2) wait:當前線程調用wait()後將被阻塞,直到另外某個線程調用notify_*喚醒當前線程;當線程被阻塞時,該函數會自動調用std::mutex的unlock()釋放鎖,使得其它被阻塞在鎖競爭上的線程得以繼續執行。一旦當前線程獲得通知(notify,通常是另外某個線程調用notify_*喚醒了當前線程),wait()函數也是自動調用std::mutex的lock()。wait分爲無條件被阻塞和帶條件的被阻塞兩種。

無條件被阻塞:調用該函數前,當前線程應該已經對unique_lock<mutex> lck完成了加鎖。所有使用同一個條件變量的線程必須在wait函數中使用同一個unique_lock<mutex>。該wait函數內部會自動調用lck.unlock()對互斥鎖解鎖,使得其他被阻塞在互斥鎖上的線程恢復執行。使用本函數被阻塞的當前線程在獲得通知(notified,通過別的線程調用 notify_*系列的函數)而被喚醒後,wait()函數恢復執行並自動調用lck.lock()對互斥鎖加鎖。

帶條件的被阻塞:wait函數設置了謂詞(Predicate),只有當pred條件爲false時調用該wait函數纔會阻塞當前線程,並且在收到其它線程的通知後只有當pred爲true時纔會被解除阻塞。因此,等效於while (!pred())  wait(lck).

(3) wait_for:與wait()類似,只是wait_for可以指定一個時間段,在當前線程收到通知或者指定的時間超時之前,該線程都會處於阻塞狀態。而一旦超時或者收到了其它線程的通知,wait_for返回,剩下的步驟和wait類似。

(4) wait_until:與wait_for類似,只是wait_until可以指定一個時間點,在當前線程收到通知或者指定的時間點超時之前,該線程都會處於阻塞狀態。而一旦超時或者收到了其它線程的通知,wait_until返回,剩下的處理步驟和wait類似。

(5) notify_all: 喚醒所有的wait線程,如果當前沒有等待線程,則該函數什麼也不做。

(6) notify_one:喚醒某個wait線程,如果當前沒有等待線程,則該函數什麼也不做;如果同時存在多個等待線程,則喚醒某個線程是不確定的(unspecified)。

條件變化存在虛假喚醒的情況,因此在線程被喚醒後需要檢查條件是否滿足。無論是notify_one或notify_all都是類似於發出脈衝信號,如果對wait的調用發生在notify之後是不會被喚醒的,所以接收者在使用wait等待之前也需要檢查條件是否滿足。

下面兩段沒看:

離開臨界區的線程用notify操作解除阻塞(unblock)在條件變量上的各個線程時,按照公平性(fairness)這些線程應該有平等的獲得互斥鎖的機會,不應讓某個線程始終難以獲得互斥鎖被餓死(starvation),並且比後來到臨界區的其它線程更爲優先(即基本上FIFO)。一種辦法是調用了notify_all的線程保持互斥鎖,直到所有從條件變量上解除阻塞的線程都已經掛起(suspend)到互斥鎖上,然後發起了notify_all的線程再釋放互斥鎖。互斥鎖上一般都有比較完善的阻塞線程調度算法,一般會按照線程優先級調度,相同優先級按照FIFO調度。

發起notify的線程不需要擁有互斥鎖。即將離開臨界區的線程是先釋放互斥鎖還是先notify操作解除在條件變量上掛起線程的阻塞?表面看兩種順序都可以。但一般建議是先notify操作,後對互斥鎖解鎖。因爲這既有利於上述的公平性,同時還避免了相反順序時可能的優先級倒置。這種先notify後解鎖的做法是悲觀的(pessimization),因爲被通知(notified)線程將立即被阻塞,等待通知(notifying)線程釋放互斥鎖。很多實現(特別是pthreads的很多實現)爲了避免這種”匆忙與等待”(hurry up and wait)情形,把在條件變量的線程隊列上處於等待的被通知線程直接移到互斥鎖的線程隊列上,而不喚醒這些線程。
 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章