Linux 条件变量使用细节(为何调用 pthread_cond_wait 前加锁,函数内部解锁,返回时又加锁)
一、本文目的
?首先說明,本文重點不在怎么用條件變量。這里我先列出 apue 中對于pthread_cond_wait函數的這么一段話:
調用者把鎖住的互斥量傳給函數,函數然后自動把調用線程放到等待條件的線程列表上,**對互斥量解鎖。**這就關閉了條件檢查和線程進入休眠狀態等待條件改變這兩個操作之間的時間通道,這樣線程就不會錯過條件的任何變化。pthread_cond_wait返回時,互斥量再次被鎖住。
這段話的信息量很大,其中關于互斥量的操作可以理解為以下三個點:
調用 pthread_cond_wait 前需要先對互斥量 mutex 上鎖,才能把 &mutex 傳入 pthread_cond_wait 函數。
在 pthread_cond_wait 函數內部,會首先對傳入的 mutex 解鎖。
當等待的條件到來后,pthread_cond_wait 函數內部在返回前會去鎖住傳入的 mutex 。
我當時看到這里,各種疑問,傳入前為何要鎖,傳入后為何要釋放,返回時又為何再次鎖?
本文就這三個問題進行詳細解釋。不過在此之前,我們需要了解為什么要有條件變量。即條件變量的作用。
二、為何需要條件變量
如果沒有條件變量,那么我們等待一個條件滿足則會是下面這樣的模型:
首先加鎖進入臨界區去查看條件是否滿足,不滿足則解鎖離開臨界區,睡眠一段時間再繼續循環判斷。在這種情況下如果剛離開臨界區,條件變為滿足,那么線程必須還要等一段時間重新進入臨界區才能知道條件滿足(如果在這段時間內,條件依舊一直保持滿足的話),如果這一小段時間條件又變為不滿足,那么這個線程還要繼續循環判斷。、
不斷地加鎖解鎖(會影響使用同一把鎖的其他線程),還不能第一時間收到條件滿足。這種模型既費時又開銷大。
所以條件變量的產生,正是為了不循環加鎖解鎖,并且第一時間收到條件滿足的通知。
三、三個問題
要回答那三個問題,那么首先需要明白等待與喚醒的配合。
下圖是我參考其他人的圖(原圖有誤)更正后所畫的。其實這個圖就能解釋那三個問題:pthread_cond_wait傳入前為何要鎖,傳入后為何先解鎖,以及返回前為何再鎖。不過我還是詳細解釋一下。
圖中有一個關鍵點,就是判斷條件是否滿足,是在調用 pthread_cond_wait 之前,上鎖之后,就是說 pthread_cond_wait 不具備判斷條件的能力,需要我們在外部寫判斷語句。
條件不滿足時,才會進入 pthread_cond_wait 。
進入 pthread_cond_wait 先解鎖就馬上阻塞。
pthread_cond_signal 喚醒的是阻塞在 pthread_cond_wait 的進程。
可以結合下面的代碼會更清楚。
以下 pthread_cond_wait 和 pthread_cond_signal 的通常用法的偽代碼(條件為:value 是不是大于 0:
lock(&mutex); while(value<=0)//需要value>0所以 value<=0就條件不滿足 {pthread_cond_wait(&cond,&mutex);//條件滿足,進行相關處理。 } unlock(&mutex); lock(&mutex); if(value==0) {value++; } if(value>0) {pthread_cond_signal(&cond); } unlock(&mutex);把這個基本流程弄清楚后,就可以解釋那三個問題了。
四、傳入前鎖 mutex
為了方便大家觀看,每個問題的解釋我都會再次把上圖貼出。
傳入前鎖 mutex 是為了保證線程從條件判斷到進入 pthread_cond_wait 前,條件不被改變。
如果沒有傳入前的鎖。就會有這樣的情況:線程 A 判斷條件不滿足之后,調用 pthread_cond_wait 之前,A 因為休眠,或者因為多線程下,多個線程執行順序和快慢的因素,令線程 B 更改了條件,使得條件滿足。但此時線程 A 還沒有調用 pthread_cond_wait。等到線程 A 又啟動調用 pthread_cond_wait 后雖然條件滿足,但卻收不到 pthread_cond_signal 的喚醒,就一直阻塞下去。
五、傳入后解鎖 mutex
傳入后解鎖是為了條件能夠被改變。
傳入后的解鎖,是因為調用 pthread_cond_signal 的那部分,需要先加鎖更改條件后才調用pthread_cond_signal。(更改條件與等待條件滿足,都是針對條件這一個資源的競爭,所以調用 pthread_cond_wait 和調用 pthread_cond_signal 的兩個線程需要同一把鎖)
如果 pthread_cond_wait 內不對 mutex 解鎖,那么在調用 pthread_cond_wait 后,其他線程就不能更改條件,條件就會一直不滿足。
六、返回前再次鎖 mutex
返回前再次鎖 mutex 是為了保證線程從 pthread_cond_wait 返回后到再次條件判斷前不被改變。
保證在 pthread_cond_signal 之后與解鎖 mutex 之間可能需要的其他語句能夠執行。
對于 1,這里的理由與傳入 pthread_cond_wait 前鎖 mutex 的理由差不多。如果不鎖,那么線程 A 調用 pthread_cond_wait 后,條件滿足,線程 A 被喚醒,從 pthread_cond_wait 返回。線程 B 在此時更改了條件,使得條件不滿足。線程 A 并不知道條件又被更改,還是以為條件滿足,就可能出錯。
對于 2,只要在 pthread_cond_signal 之后與解鎖 mutex 之間有其他語句需要執行,那么由于 mutex 在這時已經被這個線程鎖,還沒有解鎖,所以調用 pthread_cond_wait 的那個線程在pthread_cond_wait 返回前的鎖 mutex 的行為就會阻塞,直到 pthread_cond_signal 后的語句執行完解鎖,pthread_cond_wait 才會返回。
說到這里就順便說一下,由于pthread_cond_wait返回再次鎖的行為,pthread_cond_signal不一定放在 lock()和unlock()中間。
pthread_cond_signal的兩種寫法
缺點:在某些線程的實現中,會造成等待線程從內核中喚醒(由于cond_signal)回到用戶空間,然后 pthread_cond_wait 返回前需要加鎖,但是發現鎖沒有被釋放,又回到內核空間所以一來一回會有性能的問題。
但是在 LinuxThreads 或者 NPTL 里面,就不會有這個問題,因為在 Linux 線程中,有兩個隊列,分別是 cond_wait 隊列和 mutex_lock 隊列, cond_signal 只是讓線程從 cond_wait 隊列移到 mutex_lock隊列,而不用返回到用戶空間,不會有性能的損耗。所以Linux中這樣用沒問題。
lock(&mutex); //一些操作 unlock(&mutex); pthread_cond_signal(&cond);優點:不會出現之前說的那個潛在的性能損耗,因為在 signal 之前就已經釋放鎖了。
缺點:如果 unlock 之后 signal 之前,發生進程交換,另一個進程(不是等待條件的進程)拿到這把夢寐以求的鎖后加鎖操作,那么等最終切換到等待條件的線程時鎖被別人拿去還沒歸還,只能繼續等待。
七、尾語
總的來說,條件變量帶個鎖的目的就是讓等待條件成立的線程不會丟掉條件成立的情況。
以上
轉載于:https://blog.csdn.net/shichao1470/article/details/89856443
(SAW:Game Over!)
總結
以上是生活随笔為你收集整理的Linux 条件变量使用细节(为何调用 pthread_cond_wait 前加锁,函数内部解锁,返回时又加锁)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 用 openssl 生成 SSL 使用的
- 下一篇: http / 关于长连接和短链接的理解