深刻理解pthread_cond_wait、pthread_cond_signal

LINUX環境下多線程編程確定會遇到須要條件變量的狀況,此時必然要使用pthread_cond_wait()函數。但這個函數的執行過程比較難於理解。
    pthread_cond_wait()的工做流程以下(以MAN中的EXAMPLE爲例):
       Consider two shared variables x and y, protected by the mutex mut, and a condition vari-
       able cond that is to be signaled whenever x becomes greater than y.html

              int x,y;
              pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER;
              pthread_cond_t cond = PTHREAD_COND_INITIALIZER;linux

       Waiting until x is greater than y is performed as follows:編程

              pthread_mutex_lock(&mut);
              while (x <= y) {
                      pthread_cond_wait(&cond, &mut);
              }
              /* operate on x and y */
              pthread_mutex_unlock(&mut);多線程

       Modifications on x and y that may cause x to become greater than y should signal the con-
       dition if needed:app

              pthread_mutex_lock(&mut);
              /* modify x and y */
              if (x > y) pthread_cond_broadcast(&cond);
              pthread_mutex_unlock(&mut);ide

     這個例子的意思是,兩個線程要修改X和 Y的值,第一個線程當X<=Y時就掛起,直到X>Y時才繼續執行(由第二個線程可能會修改X,Y的值,當X>Y時喚醒第一個線程),即 首先初始化一個普通互斥量mut和一個條件變量cond。以後分別在兩個線程中分別執行以下函數體:函數

              pthread_mutex_lock(&mut);
              while (x <= y) {
                      pthread_cond_wait(&cond, &mut);
              }
              /* operate on x and y */
              pthread_mutex_unlock(&mut);oop

和:       pthread_mutex_lock(&mut);
              /* modify x and y */
              if (x > y) pthread_cond_signal(&cond);
              pthread_mutex_unlock(&mut); 
    其實函數的執行過程很是簡單,在第一個線程執行到pthread_cond_wait(&cond,&mut)時,此時若是X<=Y,則此函數就將mut互斥量解鎖 ,再將cond條件變量加鎖 ,此時第一個線程掛起 (不佔用任何CPU週期)。
    而在第二個線程中,原本由於mut被第一個線程鎖住而阻塞,此時由於mut已經釋放,因此能夠得到鎖mut,而且進行修改X和Y的值,在修改以後,一個IF語句斷定是否是X>Y,若是是,則此時pthread_cond_signal()函數會喚醒第一個線程 ,並在下一句中釋放互斥量mut。而後第一個線程開始從pthread_cond_wait()執行,首先要再次鎖mut , 若是鎖成功,再進行條件的判斷 (至於爲何用WHILE,即在被喚醒以後還要再判斷,後面有緣由分析),若是知足條件,則被喚醒 進行處理,最後釋放互斥量mut 。性能

    至於爲何在被喚醒以後還要再次進行條件判斷(即爲何要使用while循環來判斷條件),是由於可能有「驚羣效應」。有人以爲此處既然是被喚醒的,確定 是知足條件了,其實否則。若是是多個線程都在等待這個條件,而同時只能有一個線程進行處理,此時就必需要再次條件判斷,以使只有一個線程進入臨界區處理。 對此,轉來一段:ui

引用下POSIX的RATIONALE: 

Condition Wait Semantics 

It is important to note that when pthread_cond_wait() and pthread_cond_timedwait() return without error, the associated predicate may still be false. Similarly, when pthread_cond_timedwait() returns with the timeout error, the associated predicate may be true due to an unavoidable race between the expiration of the timeout and the predicate state change. 

The application needs to recheck the predicate on any return because it cannot be sure there is another thread waiting on the thread to handle the signal, and if there is not then the signal is lost. The burden is on the application to check the predicate. 

Some implementations, particularly on a multi-processor, may sometimes cause multiple threads to wake up when the condition variable is signaled simultaneously on different processors. 

In general, whenever a condition wait returns, the thread has to re-evaluate the predicate associated with the condition wait to determine whether it can safely proceed, should wait again, or should declare a timeout. A return from the wait does not imply that the associated predicate is either true or false. 

It is thus recommended that a condition wait be enclosed in the equivalent of a "while loop" that checks the predicate. 

從上文能夠看出: 
1,pthread_cond_signal在多處理器上可能同時喚醒多個線程,當你只能讓一個線程處理某個任務時,其它被喚醒的線程就須要繼續 wait,while循環的意義就體如今這裏了,並且規範要求pthread_cond_signal至少喚醒一個pthread_cond_wait上 的線程,其實有些實現爲了簡單在單處理器上也會喚醒多個線程. 
2,某些應用,如線程池,pthread_cond_broadcast喚醒所有線程,但咱們一般只須要一部分線程去作執行任務,因此其它的線程須要繼續wait.因此強烈推薦此處使用while循環.

       其實說白了很簡單,就是pthread_cond_signal()也可能喚醒多個線程,而若是你同時只容許一個線程訪問的話,就必需要使用while來進行條件判斷,以保證臨界區內只有一個線程在處理。

 

pthread_cond_wait()  用於阻塞當前線程,等待別的線程使用 pthread_cond_signal() 或pthread_cond_broadcast來喚醒它 。  pthread_cond_wait()   必須與pthread_mutex 配套使用。pthread_cond_wait() 函數一進入wait狀態就會自動release mutex。當其餘線程經過 pthread_cond_signal() 或pthread_cond_broadcast ,把該線程喚醒,使 pthread_cond_wait()經過(返回)時,該線程又自動得到該mutex 。
        pthread_cond_signal 函數的做用是發送一個信號給另一個正在處於阻塞等待狀態的線程,使其脫離阻塞狀態,繼續執行.若是沒有線程處在阻塞等待狀態,pthread_cond_signal也會成功返回。
        使用pthread_cond_signal通常不會有「驚羣現象」產生,他最多隻給一個線程發信號。假若有多個線程正在阻塞等待着這個條件變量的話,那麼是根據各等待線程優先級的高低肯定哪一個線程接收到信號開始繼續執行。若是各線程優先級相同,則根據等待時間的長短來肯定哪一個線程得到信號。但不管如何一個pthread_cond_signal調用最多發信一次。
        可是 pthread_cond_signal 在多處理器上可能同時喚醒多個線程,當你只能讓一個線程處理某個任務時,其它被喚醒的線程就須要繼續 wait,

 

 

==============================另外一篇很好的文章===========================

POSIX線程詳解

==============================使用效率問題============================

 

pthread_cond_signal函數的做用是發送一個信號給另一個正在處於阻塞等待狀態的線程,使其脫離阻塞狀態,繼續執行.若是沒有線程處在阻塞等待狀態,pthread_cond_signal也會成功返回。
但使用pthread_cond_signal不會有「驚羣現象」產生,他最多隻給一個線程發信號。假若有多個線程正在阻塞等待着這個條件變量的話,那麼是根據各等待線程優先級的高低肯定哪一個線程接收到信號開始繼續執行。若是各線程優先級相同,則根據等待時間的長短來肯定哪一個線程得到信號。但不管如何一個pthread_cond_signal調用最多發信一次。
另外,互斥量的做用通常是用於對某個資源進行互斥性的存取,不少時候是用來保證操做是一個原子性的操做,是不可中斷的。
用法:
pthread_cond_wait必須放在pthread_mutex_lock和pthread_mutex_unlock之間,由於他要根據共享變量的狀態來決定是否要等待,而爲了避免永遠等待下去因此必需要在lock/unlock隊中
共享變量的狀態改變必須遵照lock/unlock的規則
pthread_cond_signal便可以放在pthread_mutex_lock和pthread_mutex_unlock之間,也能夠放在pthread_mutex_lock和pthread_mutex_unlock以後,可是各有各缺點。
之間:
pthread_mutex_lock
xxxxxxx
pthread_cond_signal
pthread_mutex_unlock
缺點:在某下線程的實現中,會形成等待線程從內核中喚醒(因爲cond_signal)而後又回到內核空間(由於cond_wait返回後會有原子加鎖的行爲),因此一來一回會有性能的問題。
在code review中,我會發現不少人喜歡在pthread_mutex_lock()和pthread_mutex_unlock(()之間調用 pthread_cond_signal或者pthread_cond_broadcast函數,從邏輯上來講,這種使用方法是徹底正確的。可是在多線程環境中,這種使用方法多是低效的。posix1標準說,pthread_cond_signal與pthread_cond_broadcast無需考慮調用線程是不是mutex的擁有者,也就是說,能夠在lock與unlock之外的區域調用。若是咱們對調用行爲不關心,那麼請在lock區域以外調用吧。這裏舉個例子:
       咱們假設系統中有線程1和線程2,他們都想獲取mutex後處理共享數據,再釋放mutex。請看這種序列:
       1)線程1獲取mutex,在進行數據處理的時候,線程2也想獲取mutex,可是此時被線程1所佔用,線程2進入休眠,等待mutex被釋放。
       2)線程1作完數據處理後,調用pthread_cond_signal()喚醒等待隊列中某個線程,在本例中也就是線程2。線程1在調用pthread_mutex_unlock()前,由於系統調度的緣由,線程2獲取使用CPU的權利,那麼它就想要開始處理數據,可是在開始處理以前,mutex必須被獲取,很遺憾,線程1正在使用mutex,因此線程2被迫再次進入休眠。
       3)而後就是線程1執行pthread_mutex_unlock()後,線程2方能被再次喚醒。
       從這裏看,使用的效率是比較低的,若是再多線程環境中,這種狀況頻繁發生的話,是一件比較痛苦的事情。
可是在LinuxThreads或者NPTL裏面,就不會有這個問題,由於在Linux 線程中,有兩個隊列,分別是cond_wait隊列和mutex_lock隊列, cond_signal只是讓線程從cond_wait隊列移到mutex_lock隊列,而不用返回到用戶空間,不會有性能的損耗。
因此在Linux中推薦使用這種模式。
以後:
pthread_mutex_lock
xxxxxxx
pthread_mutex_unlock
pthread_cond_signal
優勢:不會出現以前說的那個潛在的性能損耗,由於在signal以前就已經釋放鎖了
缺點:若是unlock和signal以前,有個低優先級的線程正在mutex上等待的話,那麼這個低優先級的線程就會搶佔高優先級的線程(cond_wait的線程),而這在上面的放中間的模式下是不會出現的。
因此,在Linux下最好pthread_cond_signal放中間,但從編程規則上說,其餘兩種均可以
 
 
轉自:https://www.cnblogs.com/x_wukong/p/7909895.html
相關文章
相關標籤/搜索