訂閱
糾錯
加入自媒體

一文搞懂多線程中各個難點

6.3.4互斥量的解鎖

int pthread_mutex_unlock(pthread_mutex_t *mutex);

對上述所有的加鎖接口,都可使用該函數(shù)解鎖

解鎖的時候,會將互斥鎖當(dāng)中計數(shù)器的值從0變?yōu)?,表示其它線程可以獲取互斥量

6.4互斥鎖的本質(zhì)

1、在互斥鎖內(nèi)部有一個計數(shù)器,其實就是互斥量,計數(shù)器的值只能為0或者為1

2、當(dāng)線程獲取互斥鎖的時候,如果計數(shù)器當(dāng)前值為0,表示當(dāng)前線程不能獲取到互斥鎖,也就是沒有獲取到互斥鎖,就不要去訪問臨界資源

3、當(dāng)前線程獲取互斥鎖的時候,如果計數(shù)器當(dāng)前值為1,表示當(dāng)前線程可以獲取到互斥鎖,也就是意味著可以訪問臨界資源

6.5互斥鎖中的計數(shù)器如何保證了原子性?

獲取鎖資源的時候(加鎖):

1、寄存器當(dāng)中值直接賦值為0

2、將寄存器當(dāng)中的值和計數(shù)器當(dāng)中的值進(jìn)行交換

3、判斷寄存器當(dāng)中的值,得出加鎖結(jié)果

兩種情況:

例:4個線程,對同一個全局變量進(jìn)行減減操作

#include

互斥鎖是不公平的。

內(nèi)核維護(hù)等待隊列, 互斥量實現(xiàn)了大體上的公平;由于等待線程被喚醒后, 并不自動持有互斥量, 需要和剛進(jìn)入臨界區(qū)的線程競爭(搶鎖), 所以互斥量并沒有做到先來先服務(wù)。

6.7互斥鎖的類型

1、PTHREAD_MUTEX_NORMAL:最普通的一種互斥鎖。它不具備死鎖檢測功能, 如線程對自己鎖定的互斥量再次加鎖, 則會發(fā)生死鎖。

2、
PTHREAD_MUTEX_RECURSIVE_NP:支持遞歸的一種互斥鎖, 該互斥量的內(nèi)部維護(hù)有互斥鎖的所有者和一個鎖計數(shù)器。當(dāng)線程第一次取到互斥鎖時, 會將鎖計數(shù)器置1, 后續(xù)同一個線程再次執(zhí)行加鎖操作時, 會遞增該鎖計數(shù)器的值。解鎖則遞減該鎖計數(shù)器的值, 直到降至0, 才會真正釋放該互斥量, 此時其他線程才能獲取到該互斥量。解鎖時, 如果互斥量的所有者不是調(diào)用解鎖的線程, 則會返回EPERM。

3、
PTHREAD_MUTEX_ERRORCHECK_NP:支持死鎖檢測的互斥鎖;コ饬康膬(nèi)部會記錄互斥鎖的當(dāng)前所有者的線程ID(調(diào)度域的線程ID) 。如果互斥量的持有線程再次調(diào)用加鎖操作, 則會返回EDEADLK。解鎖時, 如果發(fā)現(xiàn)調(diào)用解鎖操作的線程并不是互斥鎖的持有者, 則會返回EPERM。

4、自旋鎖,自旋鎖采用了和互斥量完全不同的策略, 自旋鎖加鎖失敗, 并不會讓出CPU, 而是不停地嘗試加鎖, 直到成功為止。這種機(jī)制在臨界區(qū)非常小且對臨界區(qū)的爭奪并不激烈的場景下, 效果非常好。自旋鎖的效果好, 但是副作用也大, 如果使用不當(dāng), 自旋鎖的持有者遲遲無法釋放鎖, 那么, 自旋接近于死循環(huán), 會消耗大量的CPU資源, 造成CPU使用率飆高。因此, 使用自旋鎖時, 一定要確保臨界區(qū)盡可能地小, 不要有系統(tǒng)調(diào)用, 不要調(diào)用sleep。使用strcpy/memcpy等函數(shù)也需要謹(jǐn)慎判斷操作內(nèi)存的大小, 以及是否會引起缺頁中斷。

5、PTHREAD_MUTEX_ADAPTIVE_NP:自適應(yīng)鎖,首先與自旋鎖一樣, 持續(xù)嘗試獲取, 但過了一定時間仍然不能申請到鎖, 就放棄嘗試, 讓出CPU并等待。PTHREAD_MUTEX_ADAPTIVE_NP類型的互斥量, 采用的就是這種機(jī)制。

6.8死鎖和活鎖

線程1已經(jīng)成功拿到了互斥量1, 正在申請互斥量2, 而同時在另一個CPU上,線程2已經(jīng)拿到了互斥量2, 正在申請互斥量1。彼此占有對方正在申請的互斥量,結(jié)局就是誰也沒辦法拿到想要的互斥量, 于是死鎖就發(fā)生了。

6.8.1死鎖概念

死鎖是指在一組進(jìn)程中的各個進(jìn)程均占有不會釋放的資源,但因互相申請被其它進(jìn)程所占有不會釋放的資源而處于一種永久等待的狀態(tài)。

6.8.2死鎖的四個必要條件

1、互斥條件:一個資源只能被一個執(zhí)行流使用

2、請求與保持條件:一個執(zhí)行流因請求資源而阻塞時,對已獲得的資源不會釋放

3、不剝奪條件:一個執(zhí)行流已獲得的資源,在未使用完之前,不能強(qiáng)行剝奪

4、循環(huán)等待條件:若干執(zhí)行流之間形成一種頭尾相接的循環(huán)等待資源的關(guān)系

6.8.3避免死鎖

1、破壞死鎖的四個必要條件(實際上只能破壞條件2和4)

2、加鎖順序一致(按照先后順序申請互斥鎖)

3、避免未釋放鎖的情況

4、資源一次性分配

6.8.4活鎖

避免死鎖的另一種方式是嘗試一下,如果取不到鎖就返回。

int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex,const struct timespec *restrict abs_timeout);

這兩個函數(shù)反映了一種,不行就算了的思想。

trylock不行就回退的思想有可能會引發(fā)活鎖(live lock) 。生活中也經(jīng)常遇到兩個人迎面走來, 雙方都想給對方讓路, 但是讓的方向卻不協(xié)調(diào), 反而互相堵住的情況 ;铈i現(xiàn)象與這種場景有點類似。

線程1首先申請鎖mutex_a后, 之后嘗試申請mutex_b, 失敗以后, 釋放mutex_a進(jìn)入下一輪循環(huán), 同時線程2會因為嘗試申請mutex_a失敗,而釋放mutex_b, 如果兩個線程恰好一直保持這種節(jié)奏, 就可能在很長的時間內(nèi)兩者都一次次地擦肩而過。當(dāng)然這畢竟不是死鎖, 終究會有一個線程同時持有兩把鎖而結(jié)束這種情況。盡管如此, 活鎖的確會降低性能。

6.8.5死鎖調(diào)試

查看多個線程堆棧:thread apply all bt
跳轉(zhuǎn)到線程中:t 線程號
查看具體的調(diào)用堆棧:f 堆棧號
直接從pid號用gdb調(diào)試:gdb attach pid
#include

在上述代碼中,一定會出現(xiàn)死鎖,線程1拿到了互斥鎖1,又再去申請線程2的互斥鎖2,線程2拿到了互斥鎖2又再去申請線程1的互斥鎖1。

開始調(diào)試:

1、找到進(jìn)程號

2、開始調(diào)試

3、查看多個線程堆棧

4、跳轉(zhuǎn)到線程中

5、查看具體調(diào)用堆棧

6、查看互斥鎖1和互斥鎖2,分別被誰拿著

<上一頁  1  2  3  4  下一頁>  余下全文
聲明: 本文由入駐維科號的作者撰寫,觀點僅代表作者本人,不代表OFweek立場。如有侵權(quán)或其他問題,請聯(lián)系舉報。

發(fā)表評論

0條評論,0人參與

請輸入評論內(nèi)容...

請輸入評論/評論長度6~500個字

您提交的評論過于頻繁,請輸入驗證碼繼續(xù)

暫無評論

暫無評論

人工智能 獵頭職位 更多
掃碼關(guān)注公眾號
OFweek人工智能網(wǎng)
獲取更多精彩內(nèi)容
文章糾錯
x
*文字標(biāo)題:
*糾錯內(nèi)容:
聯(lián)系郵箱:
*驗 證 碼:

粵公網(wǎng)安備 44030502002758號