線程同步,條件變量pthread_cond_wait


與互斥鎖不同,條件變量是用來等待而不是用來上鎖的。條件變量用來自動阻塞一個線程,直到某特殊情況發生為止。條件變量使我們可以睡眠等待某種條件出現。
條件變量是利用線程間共享的全局變量進行同步的一種機制,
主要包括兩個動作:
一個線程等待"條件變量的條件成立"而掛起;另一個線程使"條件成立"(給出條件成立信號)。

條件的檢測是在互斥鎖的保護下進行的。如果條件為假,一個線程自動阻塞,並釋放等待狀態改變的互斥鎖。

pthread_cond_wait 原子調用: 等待條件變量, 解除鎖, 然后阻塞 
當 pthread_cond_wait 返回,則條件變量有信號,同時上鎖

等待條件有兩種方式:
條件等待pthread_cond_wait()和計時等待pthread_cond_timedwait(), 
其中計時等待方式如果在給定時刻前條件沒有滿足,則返回ETIMEOUT

無論哪種等待方式,都必須和一個互斥鎖配合,以防止多個線程同時請求pthread_cond_wait() 
(或pthread_cond_timedwait(),下同)的競爭條件(Race Condition)。

mutex互斥鎖必須是普通鎖(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()則激活所有等待線程(驚群)。

 

1 簡介
當多個線程之間因為存在某種依賴關系,導致只有當某個條件存在時,才可以執行某個線程,此時條件變量(pthread_cond_t)可以派上用場。
例1: 當系統不忙(這是一個條件)時,執行掃描文件狀態的線程。
例2: 多個線程組成線程池,只有當任務隊列中存在任務時,才用其中一個線程去執行這個任務。為避免驚群(thrundering herd),可以采用條件變量同步線程池中的線程。

 

2 用法
條件變量(pthread_cond_t)必須與鎖(pthread_mutex_t)一起使用。

條件變量的API:

1) pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);
使用 cond_attr 指定的屬性初始化條件變量 cond,當 cond_attr 為 NULL 時,使用缺省的屬性。LinuxThreads 實現條件變量不支持屬性,
因此 cond_attr 參數實際被忽略。pthread_cond_t 類型的變量也可以用 PTHREAD_COND_INITIALIZER 常量進行靜態初始化。

2) int pthread_cond_signal(pthread_cond_t *cond); / int pthread_cond_broadcast(pthread_cond_t *cond);
調用pthread_cond_signal后要立刻釋放互斥鎖,因為pthread_cond_wait的最后一步是要將指定的互斥量重新鎖住,如果pthread_cond_signal之后沒有釋放互斥鎖,pthread_cond_wait仍然要阻塞。
pthread_cond_signal使在條件變量上等待的線程中的一個線程重新開始。如果沒有等待的線程,則什么也不做。如果有多個線程在等待該條件,只有一個能重啟動。
在多處理器上,該函數是可能同時喚醒多個線程
pthread_cond_broadcast 重啟動等待該條件變量的所有線程。如果沒有等待的線程,則什么也不做。

3) int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex); /
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);
pthread_cond_wait 自動解鎖互斥量(//如果不釋放,其他線程永遠改變不了條件,條件永不能為真,該線程將一直阻塞下去)(如同執行了 pthread_unlock_mutex),
並等待條件變量觸發。這時線程掛起,不占用 CPU 時間,直到條件變量被觸發。在調用 pthread_cond_wait 之前,應用程序必須加鎖互斥量。
pthread_cond_wait 函數返回前,自動重新對互斥量加鎖(如同執行了 pthread_lock_mutex)。即條件變量觸發--->(如果互斥鎖被其他線程加鎖,阻塞等到解鎖)對互斥量加鎖---->然后返回。(可能不是十分正確)
互斥量的解鎖和在條件變量上掛起都是自動進行的。因此,在條件變量被觸發前,如果所有的線程都要對互斥量加鎖,
這種機制可保證在線程加鎖互斥量和進入等待條件變量期間,條件變量不被觸發。

pthread_cond_timedwait 和 pthread_cond_wait 一樣,自動解鎖互斥量及等待條件變量,但它還限定了等待時間
如果當前沒有線程等待通知,則上面兩種調用實際上成為一個空操作。

 

4) int pthread_cond_destroy(pthread_cond_t *cond);
銷毀一個條件變量,釋放它擁有的資源。進入 pthread_cond_destroy 之前,必須沒有在該條件變量上等待的線程。否則返回EBUSY
在 LinuxThreads 的實現中,條件變量不聯結資源,除檢查有沒有等待的線程外,pthread_cond_destroy 實際上什么也不做。

 

3. 取消
pthread_cond_wait 和 pthread_cond_timedwait 是取消點。如果一個線程在這些函數上掛起時被取消,線程立即繼續執行,
然后再次對 pthread_cond_wait 和 pthread_cond_timedwait 在 mutex 參數加鎖,最后執行取消。因此,當調用清除處理程序時,可確保,mutex 是加鎖的。


4. 異步信號安全(Async-signal Safety)

條件變量函數不是異步信號安全的,不應當在信號處理程序中進行調用。特別要注意,如果在信號處理程序中調用
pthread_cond_signal 或 pthread_cond_boardcast 函數,可能導致調用線程死鎖。

 

5 避免驚群
每當一塊肉丟到狼群,就引發一群狼去爭搶,但最后只有一只狼得到了肉。
計算機的驚群會造成服務器資源空耗。
pthread_cond_signal函數的作用是發送一個信號給另外一個正在處於阻塞等待狀態的線程,使其脫離阻塞狀態,繼續執行.
如果沒有線程處在阻塞等待狀態,pthread_cond_signal也會成功返回。
但使用pthread_cond_signal不會有“驚群現象”產生,它最多只給一個線程發信號。
假如有多個線程正在阻塞等待着這個條件變量的話,那么是根據各等待線程優先級的高低確定哪個線程接收到信號開始繼續執行。
如果各線程優先級相同,則根據等待時間的長短來確定哪個線程獲得信號。但無論如何一個pthread_cond_signal調用最多發信一次。

 

6、pthread_cond_signal的位置
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返回后會有原子加鎖的 行為),所以一來一回會有性能的問題。
但是在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的線程),而這在上面的放中間的模式下是不會出現的。

 

7、喚醒丟失問題
在線程並沒有阻塞在條件變量上時,調用pthread_cond_signal或pthread_cond_broadcast函數可能會引起喚醒丟失問題。

喚醒丟失往往會在下面的情況下發生:

一個線程調用pthread_cond_signal或pthread_cond_broadcast函數;
另一個線程正處在測試條件變量和調用pthread_cond_wait函數之間;
沒有線程正在處在阻塞等待的狀態下。

 

-----------------------------------------------------------------------------

取消點的問題:

pthread_cond_wait 和pthread_cond_timedwait都被實現為取消點。因此,線程被取消之后,在該處等待的線程將立即重新運行,在重新鎖定mutex后wait函數返回,然后執行取消動作。

也就是說,如果wait函數被取消,mutex將依然保持鎖定狀態,那么線程需要定義退出回調函數來為其解鎖。

使用pthread_cleanup_push() 和 pthread_cleanup_pop設置回調函數保護。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM