線程同步,條件變量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),能夠採用條件變量同步線程池中的線程。spa

 

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調用最多發信一次。

 

六、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的線程),而這在上面的放中間的模式下是不會出現的。

 

七、喚醒丟失問題
在線程並無阻塞在條件變量上時,調用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設置回調函數保護。

相關文章
相關標籤/搜索