1. 程式人生 > >訊號量與條件變數的區別

訊號量與條件變數的區別

注意訊號量與條件變數的區別

訊號量內容可見:http://www.cnblogs.com/charlesblc/p/6142868.html

 

訊號量、共享記憶體,以及訊息佇列等System V IPC三劍客主要關注程序間通訊;

而條件變數、互斥鎖,主要關注執行緒間通訊。

 

下面內容參考:http://blog.chinaunix.net/uid-27164517-id-3282242.html

 

pthread_cond_wait指的是條件變數,總和一個互斥鎖結合使用。在呼叫pthread_cond_wait前要先獲取鎖。pthread_cond_wait函式執行時先自動釋放指定的鎖,然後等待條件變數的變化。在函式呼叫返回之前,自動將指定的互斥量重新鎖住。

 

int pthread_cond_signal(pthread_cond_t * cond);

pthread_cond_signal通過條件變數cond傳送訊息,若多個訊息在等待,它只喚醒一個。

pthread_cond_broadcast可以喚醒所有。呼叫pthread_cond_signal後要立刻釋放互斥鎖,因為pthread_cond_wait的最後一步是要將指定的互斥量重新鎖住,如果pthread_cond_signal之後沒有釋放互斥鎖,pthread_cond_wait仍然要阻塞。

 

 

無論哪種等待方式,都必須和一個互斥鎖配合,以防止多個執行緒同時請求pthread_cond_wait()(或pthread_cond_timedwait(),下同)的競爭條件(Race   Condition)。mutex互斥鎖必須是普通鎖(PTHREAD_MUTEX_TIMED_NP)或者適應鎖 (PTHREAD_MUTEX_ADAPTIVE_NP)

複製程式碼

互斥鎖的型別,有以下幾種:

  PTHREAD_MUTEX_TIMED_NP,這是預設值,也就是普通鎖。當一個執行緒加鎖以後,其餘請求鎖的執行緒將形成一個等待佇列,並在解鎖後按優先順序獲得鎖。這種鎖策略保證了資源分配的公平性。

  PTHREAD_MUTEX_RECURSIVE_NP,巢狀鎖,允許同一個執行緒對同一個鎖成功獲得多次,並通過多次unlock解鎖。如果是不同執行緒請求,則在加鎖執行緒解鎖時重新競爭。

  PTHREAD_MUTEX_ERRORCHECK_NP,檢錯鎖,如果同一個執行緒請求同一個鎖,則返回EDEADLK,否則與PTHREAD_MUTEX_TIMED_NP型別動作相同。這樣就保證當不允許多次加鎖時不會出現最簡單情況下的死鎖。

  PTHREAD_MUTEX_ADAPTIVE_NP,適應鎖,動作最簡單的鎖型別,僅等待解鎖後重新競爭。

複製程式碼

且在呼叫pthread_cond_wait()前必須由本執行緒加鎖 (pthread_mutex_lock()),而在更新條件等待佇列以前,mutex保持鎖定狀態,並在執行緒掛起進入等待前解鎖。在條件滿足從而離開 pthread_cond_wait()之前,mutex將被重新加鎖,以與進入pthread_cond_wait()前的加鎖動作對應。  

 

激發條件有兩種形式,pthread_cond_signal()啟用一個等待該條件的執行緒,存在多個等待執行緒時按入隊順序啟用其中一個;而pthread_cond_broadcast()則啟用所有等待執行緒。

 

下面是另一處說明:給出了函式執行全過程。 為什麼在喚醒執行緒後要重新mutex加鎖?

瞭解 pthread_cond_wait() 的作用非常重要 -- 它是 POSIX 執行緒訊號傳送系統的核心,也是最難以理解的部分。

首先,讓我們考慮以下情況:執行緒為檢視已連結列表而鎖定了互斥物件,然而該列表恰巧是空的。這一特定執行緒什麼也幹不了 -- 其設計意圖是從列表中除去節點,但是現在卻沒有節點。因此,它只能:

鎖定互斥物件時,執行緒將呼叫 pthread_cond_wait(&mycond,&mymutex)。

 

pthread_cond_wait() 所做的第一件事就是同時對互斥物件解鎖(於是其它執行緒可以修改已連結列表),並等待條件 mycond 發生(這樣當 pthread_cond_wait() 接收到另一個執行緒的“訊號”時,它將甦醒)。現在互斥物件已被解鎖,其它執行緒可以訪問和修改已連結列表,可能還會新增項。 【要求解鎖並阻塞是一個原子操作

 

此時,pthread_cond_wait() 呼叫還未返回。對互斥物件解鎖會立即發生,但等待條件 mycond 通常是一個阻塞操作,這意味著執行緒將睡眠,在它甦醒之前不會消耗 CPU 週期。這正是我們期待發生的情況。執行緒將一直睡眠,直到特定條件發生,在這期間不會發生任何浪費 CPU 時間的繁忙查詢。從執行緒的角度來看,它只是在等待 pthread_cond_wait() 呼叫返回。

 

現在繼續說明,假設另一個執行緒(稱作“2 號執行緒”)鎖定了 mymutex 並對已連結列表添加了一項。在對互斥物件解鎖之後,2 號執行緒會立即呼叫函式 pthread_cond_broadcast(&mycond)。此操作之後,2 號執行緒將使所有等待 mycond 條件變數的執行緒立即甦醒。這意味著第一個執行緒(仍處於 pthread_cond_wait() 呼叫中)現在將甦醒。 是先解鎖還是先signal,各有優缺點,下文會分析。另外注意,signal的函式裡面是不是解鎖加鎖的,跟wait不一樣。

 

現在,看一下第一個執行緒發生了什麼。您可能會認為在 2 號執行緒呼叫 pthread_cond_broadcast(&mymutex) 之後,1 號執行緒的 pthread_cond_wait() 會立即返回。不是那樣!實際上,pthread_cond_wait() 將執行最後一個操作:重新鎖定 mymutex。一旦 pthread_cond_wait() 鎖定了互斥物件,那麼它將返回並允許 1 號執行緒繼續執行。那時,它可以馬上檢查列表,檢視它所感興趣的更改。實際上,一般是先解鎖。

 

來看一個例子:

複製程式碼

In Thread1:
pthread_mutex_lock(&m_mutex);   
pthread_cond_wait(&m_cond,&m_mutex);   
pthread_mutex_unlock(&m_mutex);  
 
In Thread2:
pthread_mutex_lock(&m_mutex);   
pthread_cond_signal(&m_cond);   
pthread_mutex_unlock(&m_mutex);

複製程式碼

 

為什麼要與pthread_mutex 一起使用呢?

這是為了應對 執行緒1在呼叫pthread_cond_wait()但執行緒1還沒有進入wait cond的狀態的時候,此時執行緒2呼叫了 cond_singal 的情況。 如果不用mutex鎖的話,這個cond_singal就丟失了。加了鎖的情況是,執行緒2必須等到 mutex 被釋放(也就是 pthread_cod_wait() 釋放鎖並進入wait_cond狀態 ,此時執行緒2上鎖) 的時候才能呼叫cond_singal.

 

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返回後會有原子加鎖的 行為)(注:意思是說這時候signal的執行緒還沒有unlock,所以wait的執行緒加鎖會導致堵塞,並進入核心),所以一來一回會有效能的問題。但是在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的執行緒),而這在上面的放中間的模式下