多線(xiàn)程讀寫(xiě)鎖產(chǎn)生死鎖的故障解決方案
作者 | morphis
多線(xiàn)程環(huán)境下,讀寫(xiě)鎖是一種常用的同步原語(yǔ),適用于多讀者-多寫(xiě)者的經(jīng)典問(wèn)題;合理的使用可以在保證數(shù)據(jù)一致性的前提下,大幅提升讀性能,但不合理的使用可能會(huì)導(dǎo)致死鎖。本文從一次協(xié)程泄露問(wèn)題入手,分析golang讀寫(xiě)鎖可能產(chǎn)生死鎖的場(chǎng)景,希望讀者可以避坑。
一、故障背景
近期線(xiàn)上某個(gè)trpc-go服務(wù)一直在OOM,據(jù)以往查障經(jīng)驗(yàn),golang服務(wù)發(fā)生內(nèi)存持續(xù)上漲大概率是由兩個(gè)原因?qū)е?
- 請(qǐng)求量過(guò)大,服務(wù)處理不過(guò)來(lái),造成協(xié)程積壓,或者資源積壓;
- 協(xié)程泄露,由于未正確關(guān)閉、或者協(xié)程阻塞等原因,導(dǎo)致協(xié)程積壓。
123平臺(tái)容器監(jiān)控如下:
二、排查思路
1. 檢查請(qǐng)求量級(jí)
第一時(shí)間排查了一下服務(wù)的請(qǐng)求量和CPU/內(nèi)存情況,發(fā)現(xiàn)請(qǐng)求量并未上漲,內(nèi)存上漲的同時(shí),CPU并未線(xiàn)性相關(guān)上漲,但是協(xié)程數(shù)一直在上漲;這里就排除了請(qǐng)求積壓這個(gè)原因。
2. 檢查協(xié)程泄露
在請(qǐng)求量并未明顯上漲的前提下,協(xié)程數(shù)在上漲,比較符合協(xié)程泄露的現(xiàn)象;于是借助123平臺(tái)的pprof工具,采樣觀測(cè)了一下協(xié)程積壓情況,如下:
發(fā)現(xiàn)協(xié)程大量積壓在gopark函數(shù)等待上,證明大量協(xié)程正掛起等待,根據(jù)調(diào)用棧定位到業(yè)務(wù)代碼分別是對(duì)一個(gè)讀寫(xiě)鎖的讀鎖、寫(xiě)鎖加鎖行為:
(*RWMutex).Rock()
(*RWMutex).Lock()
3. 定位協(xié)程泄漏點(diǎn)
定位到這里其實(shí)已經(jīng)基本猜到是由于讀寫(xiě)鎖的阻塞導(dǎo)致協(xié)程泄露,遂review代碼,兩個(gè)競(jìng)態(tài)的協(xié)程分別按順序執(zhí)行的加鎖行為如下:
(1)協(xié)程A:讀鎖-加鎖,第一次。
(2)協(xié)程A:讀鎖-加鎖,第二次:
(3)協(xié)程B:寫(xiě)鎖-加鎖
回想操作系統(tǒng)原理中,死鎖產(chǎn)生的4個(gè)必要條件:
- 互斥(Mutual Exclusion):至少有一個(gè)資源必須處于非共享模式,也就是說(shuō),在一段時(shí)間內(nèi)只能有一個(gè)進(jìn)程使用該資源。如果其他進(jìn)程請(qǐng)求該資源,請(qǐng)求者只能等待,直到資源被釋放。
- 占有并等待(Hold and Wait):一個(gè)進(jìn)程至少持有一個(gè)資源,并且正在等待獲取額外的資源,這些額外資源被其他進(jìn)程持有。非搶占(No Preemption):資源不能被強(qiáng)制從一個(gè)進(jìn)程中搶占,只能由持有資源的進(jìn)程主動(dòng)釋放。
- 循環(huán)等待(Circular Wait):必須存在一個(gè)進(jìn)程—資源的環(huán)形鏈,其中每個(gè)進(jìn)程至少持有一個(gè)資源,但又等待另一個(gè)被鏈中下一個(gè)進(jìn)程持有的資源。
互斥、非搶占、占有并等待在現(xiàn)代編程語(yǔ)言中的同步語(yǔ)法中都是容易滿(mǎn)足的,問(wèn)題就出在循環(huán)等待上,在讀寫(xiě)鎖的使用上,經(jīng)常出現(xiàn)死鎖的情況是:
協(xié)程A:讀鎖 - 加鎖 協(xié)程A:寫(xiě)鎖 - 加鎖
一般是同一個(gè)協(xié)程在持有鎖(讀/寫(xiě))的情況下,請(qǐng)求寫(xiě)鎖,這個(gè)時(shí)候?qū)戞i因?yàn)槌钟械逆i并未釋放,而永久等待,陷入死鎖困境;回過(guò)頭去review代碼,并未出現(xiàn)這種情況,但是RLock重入了一次,仔細(xì)閱讀了golang官方文檔,禁止RLock的遞歸調(diào)用,換句話(huà)說(shuō)是在同一協(xié)程下,讀鎖未UnLock的情況下,禁止重復(fù)調(diào)用RLock(當(dāng)然也不能調(diào)用Lock),否則可能導(dǎo)致死鎖:
A RWMutex must not be copied after first use.
If any goroutine calls RWMutex.Lock while the lock is already held by one or more readers, concurrent calls to RWMutex.RLock will block until the writer has acquired (and released) the lock, to ensure that the lock eventually becomes available to the writer. Note that this prohibits recursive read-locking.
排查至此,問(wèn)題已經(jīng)明確,是有重入讀鎖導(dǎo)致的死鎖,進(jìn)而引發(fā)的協(xié)程泄露。
三、讀寫(xiě)鎖原理
1. 讀寫(xiě)鎖適用場(chǎng)景
深究死鎖產(chǎn)生的原因,不得不探究一下讀寫(xiě)鎖的適用場(chǎng)景。在大多數(shù)編程語(yǔ)言中,如果存在多線(xiàn)程對(duì)數(shù)據(jù)的競(jìng)爭(zhēng),最常用的一個(gè)方案是數(shù)據(jù)加互斥鎖/排它鎖(Mutex),互聯(lián)網(wǎng)業(yè)務(wù)特點(diǎn),在對(duì)數(shù)據(jù)的操作上,普遍是讀遠(yuǎn)多于寫(xiě),如果所有對(duì)數(shù)據(jù)的操作都互斥,即便是沒(méi)有寫(xiě)行為,大量并發(fā)的讀操作也會(huì)退化成成串行訪(fǎng)問(wèn)。這個(gè)時(shí)候就需要差異化對(duì)待讀、寫(xiě)行為,使用更‘細(xì)粒度’的鎖-讀寫(xiě)鎖(共享鎖),基本特性概括如下:
- 讀鎖和讀鎖 - 不互斥
- 讀鎖和寫(xiě)鎖 - 互斥
- 寫(xiě)鎖和寫(xiě)鎖 - 互斥
這樣就可以在持有寫(xiě)鎖時(shí),任何讀/寫(xiě)行為都會(huì)被阻塞;且未持有寫(xiě)鎖時(shí),任何讀行為都不會(huì)被阻塞;即保護(hù)了數(shù)據(jù),又保證了性能;聽(tīng)起來(lái)很“完美”的解決方案,但經(jīng)常寫(xiě)技術(shù)方案的同學(xué)都知道,沒(méi)有最好的技術(shù)方案,只有合適的技術(shù)方案。既然根據(jù)讀、寫(xiě)操作對(duì)鎖進(jìn)行了區(qū)分,那么針對(duì)不同的業(yè)務(wù)場(chǎng)景(讀密集 or 寫(xiě)密集),不同的設(shè)計(jì)取向(性能優(yōu)先 or 一致性?xún)?yōu)先),必然面臨一個(gè)問(wèn)題:讀寫(xiě)鎖獲取鎖的優(yōu)先級(jí)是怎樣的?
2. 優(yōu)先級(jí)策略
針對(duì)reader-writer問(wèn)題,基于對(duì)讀和寫(xiě)操作的優(yōu)先級(jí),讀寫(xiě)鎖的設(shè)計(jì)和實(shí)現(xiàn)也分成三類(lèi):
- Read-preferring: 讀優(yōu)先策略,可實(shí)現(xiàn)最大并發(fā)性,但如果讀操作密集,會(huì)導(dǎo)致寫(xiě)鎖饑餓。因?yàn)橹灰粋€(gè)讀取線(xiàn)程持有鎖,寫(xiě)入線(xiàn)程就無(wú)法獲取鎖。如果有源源不斷的讀操作,寫(xiě)鎖只能等待所有讀鎖釋放后才能獲取到。
- Writer-preferring:寫(xiě)優(yōu)先的策略,可以保證即便在讀密集的場(chǎng)景下,寫(xiě)鎖也不會(huì)饑餓;只要有一個(gè)寫(xiě)鎖申請(qǐng)加鎖,那么就會(huì)阻塞后續(xù)的所有讀鎖加鎖行為(已經(jīng)獲取到讀鎖的reader不受影響,寫(xiě)鎖仍然要等待這些讀鎖釋放之后才能加鎖)。
- Unspecified(不指定):不區(qū)分reader和writer優(yōu)先級(jí),中庸之道,讀寫(xiě)性能不是最優(yōu),但是可以避免饑餓問(wèn)題。
3. 源碼分析
golang標(biāo)準(zhǔn)庫(kù)里讀寫(xiě)鎖的實(shí)現(xiàn)(RWMutex),采用了writer-preferring的策略,使用Mutex實(shí)現(xiàn)寫(xiě)-寫(xiě)互斥,通過(guò)信號(hào)量等待和喚醒實(shí)現(xiàn)讀-寫(xiě)互斥,RWMutex定義如下:
type RWMutex struct {
w Mutex // 互斥鎖,用于寫(xiě)鎖互斥
writerSem uint32 // writer信號(hào)量,讀鎖RUnlock時(shí)釋放,可以喚醒等待寫(xiě)加鎖的線(xiàn)程
readerSem uint32 // reader信號(hào)量,寫(xiě)鎖Unlock時(shí)釋放,可以喚醒等待讀加鎖的線(xiàn)程
readerCount atomic.Int32 // 所有reader的數(shù)量(包括等待讀鎖和已經(jīng)獲得讀鎖)
readerWait atomic.Int32 // 已經(jīng)獲取到讀鎖的reader數(shù)量,writer需要等待這個(gè)變量歸0后才可以獲得寫(xiě)鎖
}
(1)讀鎖-加鎖
readerCount是一個(gè)有多重含義的變量,在沒(méi)有寫(xiě)鎖的情況下,每次讀鎖加鎖,都會(huì)原子+1;每次讀鎖釋放,readerCount會(huì)原子-1,所以在沒(méi)有寫(xiě)鎖的情況下readerCount=獲取到的讀鎖的reader數(shù)量;但是當(dāng)存寫(xiě)鎖pending時(shí),都會(huì)將readerCount置為負(fù)數(shù),所以這里判斷為負(fù)數(shù)時(shí),直接進(jìn)入信號(hào)量等待。
func (rw *RWMutex) RLock() {
// ...
if rw.readerCount.Add(1) < 0 {
// 當(dāng)有writer在pending時(shí),readerCount會(huì)被加一個(gè)很大的負(fù)數(shù),保證readerCount變成負(fù)數(shù)
// 有writer在等待鎖,需要等待reader信號(hào)量
runtime_SemacquireRWMutexR(&rw.readerSem, false, 0)
}
// ...
}
(2)讀鎖-釋放
reader釋放讀鎖時(shí),優(yōu)先將readerCount-1,這時(shí)如果還是負(fù)數(shù),證明有寫(xiě)鎖在pending,這個(gè)時(shí)候需要釋放信號(hào)量,以便喚醒等待寫(xiě)鎖的writer;當(dāng)然,前提需要所有已經(jīng)獲得讀鎖的reader都釋放讀鎖后(readerWait == 0),那問(wèn)題來(lái)了,為什么需要先檢查readerCount,再對(duì)readerWait-1,實(shí)際上readerWait只有在有寫(xiě)鎖在pending時(shí)才會(huì)生效,否則,readerCount就等于已經(jīng)獲得讀鎖的reader數(shù)量。
func (rw *RWMutex) RUnlock() {
// ...
if r := rw.readerCount.Add(-1); r < 0 {
// 這里將相對(duì)slow的代碼封裝起來(lái),以便RUnlock()可以被更多場(chǎng)景內(nèi)聯(lián),提升程序性能
// Outlined slow-path to allow the fast-path to be inlined
rw.rUnlockSlow(r)
}
// ...
}
func (rw *RWMutex) rUnlockSlow(r int32) {
// ...
// readerWait變量,記錄真正獲得讀鎖的reader數(shù)量,當(dāng)這個(gè)變量規(guī)0時(shí),需要釋放信號(hào)量,以便喚醒等到寫(xiě)鎖的writer
if rw.readerWait.Add(-1) == 0 {
// The last reader unblocks the writer.
runtime_Semrelease(&rw.writerSem, false, 1)
}
}
(3)寫(xiě)鎖-加鎖
寫(xiě)鎖間的互斥需要依賴(lài)互斥量,所以首先需要對(duì)w進(jìn)行競(jìng)爭(zhēng)加鎖;當(dāng)獲取到w之后,證明可以獨(dú)占寫(xiě)操作;這個(gè)時(shí)候再來(lái)檢查讀鎖的情況;這里不得不感慨golang底層庫(kù)的實(shí)現(xiàn)之精妙,在一個(gè)計(jì)數(shù)值上賦予了多重含義,將readerCount加一個(gè)巨大的、固定的負(fù)數(shù)可以保證readerCount為負(fù)數(shù),這樣就可以在標(biāo)記有一個(gè)寫(xiě)鎖在pending的同時(shí),也不會(huì)丟失readerCount的數(shù)量。
func (rw *RWMutex) Lock() {
// ...
rw.w.Lock() // 寫(xiě)鎖互斥
// 將readerCount加一個(gè)巨大的、固定的負(fù)數(shù),保證readerCount為負(fù)數(shù)
// r代表readerCount變成負(fù)數(shù)的那一刻的readCount,代表了請(qǐng)求寫(xiě)鎖那一刻'注定'能獲取讀鎖的reader數(shù)量,在此之后的reader不管怎么對(duì)readerCount+1,都會(huì)阻塞到信號(hào)量等待上;
// 所以r的值就是在加寫(xiě)鎖的那一刻,已經(jīng)獲得讀鎖的reader數(shù)量
r := rw.readerCount.Add(-rwmutexMaxReaders) + rwmutexMaxReaders
// 當(dāng)有reader已經(jīng)獲得讀鎖時(shí),需要等待信號(hào)量
if r != 0 && rw.readerWait.Add(r) != 0 {
runtime_SemacquireRWMutex(&rw.writerSem, false, 0)
}
// ...
}
r代表著在寫(xiě)鎖加鎖的那一刻(是一個(gè)瞬時(shí)值,可能會(huì)變),已經(jīng)獲得讀鎖的reader數(shù)量,通過(guò)過(guò)對(duì)readerWait賦值和判斷,決定是否需要等待信號(hào)量;那么問(wèn)題來(lái)了,既然r是一個(gè)瞬時(shí)值,如果r已經(jīng)變了,怎么保證readerWait是準(zhǔn)的,例如:
在執(zhí)行這行代碼時(shí),r=5,代表有5個(gè)reader獲得了讀鎖,此時(shí)readerWait==0:
r := rw.readerCount.Add(-rwmutexMaxReaders) + rwmutexMaxReaders
而在執(zhí)行下面這行之前,有2個(gè)reader已經(jīng)釋放讀鎖,此時(shí)readerWait==-2,再執(zhí)行下面這樣代碼后,readerWait==3,這樣設(shè)計(jì)的精妙之處就在于,不管readerWait中間如何變化,只要在使用的那一刻他是最終準(zhǔn)確的就可以,所以嚴(yán)格意義上講readerWait記錄的是已經(jīng)持有讀鎖的reader數(shù)量,或者"自有寫(xiě)鎖pending那一刻來(lái),被釋放的讀鎖的負(fù)數(shù)量"
if r != 0 && rw.readerWait.Add(r) != 0 {
這也是在讀鎖釋放時(shí),必須要判斷readerWait==0,而不是<=0的原因:
if rw.readerWait.Add(-1) == 0 {
(4)寫(xiě)鎖-釋放
寫(xiě)鎖的釋放主要做3件事,將readerCount置為正數(shù),表示新的reader可以不用等待信號(hào)量,直接獲取讀鎖了;對(duì)正在阻塞等待信號(hào)量的reader,依次喚醒;注意,這里的r也是一個(gè)瞬時(shí)值,代表著寫(xiě)鎖釋放那一刻,正在等待信號(hào)量的reader,之后不管新的reader如何對(duì)readerCount+1,writer需要喚醒的reader也停留在“寫(xiě)鎖釋放那一刻”;最后釋放互斥量,允許其他writer獲取寫(xiě)鎖。
func (rw *RWMutex) Unlock() {
// ...
// 恢復(fù)readerCount為正數(shù),表示reader可以獲取讀鎖了
r := rw.readerCount.Add(rwmutexMaxReaders)
// 省略...
// 釋放信號(hào)量,喚醒等待的reader
for i := 0; i < int(r); i++ {
runtime_Semrelease(&rw.readerSem, false, 0)
}
// 釋放互斥量,允許其他寫(xiě)鎖獲取
rw.w.Unlock()
// ...
}
golang讀寫(xiě)鎖的實(shí)現(xiàn),可以保證在沒(méi)有writer請(qǐng)求寫(xiě)鎖時(shí),讀操作可以保證最大的性能,此時(shí)的讀鎖加鎖-釋放行為,開(kāi)銷(xiāo)非常小,僅僅是原子更新readerCount;當(dāng)有writer請(qǐng)求寫(xiě)鎖時(shí),寫(xiě)鎖的加鎖-釋放行為會(huì)重一些,已經(jīng)獲得讀鎖的reader也不受影響;后續(xù)再請(qǐng)求讀鎖的reader將被阻塞,直到寫(xiě)鎖釋放,大道至簡(jiǎn)。
四、問(wèn)題總結(jié)
1. 故障原因
回到故障本身,死鎖的原因是讀鎖的重入,造成了死鎖;實(shí)際上單純的讀鎖重入,不會(huì)造成死鎖;造成死鎖的case時(shí)序可以表示如下:
- reader0:加鎖R0成功
- writer0: 加寫(xiě)鎖W0申請(qǐng),標(biāo)記已經(jīng)有writer等待寫(xiě)鎖,然后等待R0釋放,W0->R0
- reader0: 嘗試加鎖R1,發(fā)現(xiàn)已經(jīng)有writer在等待,因?yàn)閷?xiě)鎖優(yōu)先級(jí)較高,所以需要等待W0釋放,R1->W0
- reader0: 由于代碼邏輯上R0,R1是先后關(guān)系,所以R0需要依賴(lài)R1釋放,R0->R1
這樣就造成了死鎖必要的循環(huán)依賴(lài)條件:R0->R1->W0->R0
2. 解決方案
明確了故障原因,解決方案就顯而易見(jiàn)了,避免重復(fù)調(diào)用鎖就可以避免此類(lèi)死鎖的發(fā)生,但函數(shù)調(diào)用靈活復(fù)雜的,無(wú)法避免函數(shù)的嵌套調(diào)用,目前也沒(méi)有效的手段可以在編譯期發(fā)現(xiàn)這個(gè)問(wèn)題;但我們可以通過(guò)一些范式來(lái)盡量避免,總結(jié)而言就是-臨界區(qū)一定要?。?/p>
- 鎖的粒度一定要小,一方面避免粒度過(guò)大造成鎖忘記釋放,另一方面避免臨界區(qū)過(guò)大造成資源浪費(fèi)。
- 避免在臨界區(qū)嵌套調(diào)用函數(shù),一般情況下,需要加鎖的情況無(wú)外乎對(duì)數(shù)據(jù)的讀和寫(xiě)操作,應(yīng)當(dāng)在一眼所視范圍內(nèi)對(duì)數(shù)據(jù)進(jìn)行讀寫(xiě)操作,而不是通過(guò)調(diào)用函數(shù)的方式。
- defer釋放鎖要格外小心,defer可以方便的釋放鎖,可以避免忘記釋放鎖,可能第一版代碼臨界區(qū)就幾行,隨著后續(xù)維護(hù)者的拓展,臨界區(qū)變成了幾十行、上百行,這個(gè)時(shí)候其實(shí)就需要review一下,鎖的保護(hù)范圍到底是誰(shuí),是否粒度過(guò)大;若粒度過(guò)大,就應(yīng)該棄用defer,主動(dòng)盡早釋放鎖。
3. 拓展思考
(1) golang
在排查故障的過(guò)程中,了解到golang RWMutext的使用還有幾個(gè)可能踩到的坑:
- 不可復(fù)制,復(fù)制可能導(dǎo)致內(nèi)部計(jì)數(shù)值readCount、readWait的原子性遭到破壞,進(jìn)而導(dǎo)致信號(hào)量的等待/釋放錯(cuò)亂,最后導(dǎo)致鎖被重復(fù)釋放,或者鎖永遠(yuǎn)被持有無(wú)法釋放等異常現(xiàn)象。
- 重入讀鎖導(dǎo)致死鎖(本文所示)
- 釋放未加鎖的RWMutex,或重復(fù)釋放,本質(zhì)上是對(duì)RUnlock/Unlock操作的重入,RLock/Lock的重入會(huì)導(dǎo)致死鎖,重復(fù)解鎖會(huì)導(dǎo)致panic,所以RLock/RUnlock、Lock/Unlock一定要成對(duì)出現(xiàn)。
(2) pthread-c
讀寫(xiě)鎖并不是golang的原創(chuàng),posix標(biāo)準(zhǔn)線(xiàn)程庫(kù)里也有對(duì)應(yīng)的實(shí)現(xiàn):
int pthread_rwlock_rdlock(pthread_rwlock_t *rwptr);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwptr);
int pthread_rwlock_unlock(pthread_rwlock_t *rwptr);
posix標(biāo)準(zhǔn)并未指定讀寫(xiě)鎖的優(yōu)先級(jí),但允許實(shí)現(xiàn)者采用writer-preferring來(lái)避免writer饑餓問(wèn)題,
The pthread_rwlock_rdlock() function applies a read lock to the read-write lock referenced by rwlock. The calling thread acquires the read lock if a writer does not hold the lock and there are no writers blocked on the lock. It is unspecified whether the calling thread acquires the lock when a writer does not hold the lock and there are writers waiting for the lock. If a writer holds the lock, the calling thread will not acquire the read lock. If the read lock is not acquired, the calling thread blocks (that is, it does not return from the pthread_rwlock_rdlock() call) until it can acquire the lock. Results are undefined if the calling thread holds a write lock on rwlock at the time the call is made.
mplementations are allowed to favour writers over readers to avoid writer starvation
粗略追了一下glibc的源碼,pthread讀寫(xiě)鎖的實(shí)現(xiàn)也是采用writer-preferring(__nr_writers_queued記錄正在等待寫(xiě)鎖的writer)。
/* Acquire read lock for RWLOCK. */
int
__pthread_rwlock_rdlock (rwlock)
pthread_rwlock_t *rwlock;
{
int result = 0;
LIBC_PROBE (rdlock_entry, 1, rwlock);
/* Make sure we are alone. */
lll_lock (rwlock->__data.__lock, rwlock->__data.__shared);
while (1)
{
/* Get the rwlock if there is no writer... */
if (rwlock->__data.__writer == 0
/* ...and if either no writer is waiting or we prefer readers. */
&& (!rwlock->__data.__nr_writers_queued
|| PTHREAD_RWLOCK_PREFER_READER_P (rwlock)))
{
/* Increment the reader counter. Avoid overflow. */
if (__builtin_expect (++rwlock->__data.__nr_readers == 0, 0))
{
/* Overflow on number of readers. */
--rwlock->__data.__nr_readers;
result = EAGAIN;
}
(3) C++
C++ 17提供了“共享鎖”這種語(yǔ)義的同步原語(yǔ)shared_mutex,類(lèi)似于讀寫(xiě)鎖,通過(guò)對(duì)shared_mutex加獨(dú)占鎖lock()和共享鎖lock_shared()來(lái)實(shí)現(xiàn)讀寫(xiě)鎖語(yǔ)義,不過(guò)標(biāo)準(zhǔn)本身也沒(méi)有定義讀寫(xiě)鎖優(yōu)先級(jí)。
Acquires shared ownership of the mutex. If another thread is holding the mutex in exclusive ownership, a call to lock_shared will block execution until shared ownership can be acquired.
If lock_shared is called by a thread that already owns the mutex in any mode (exclusive or shared), the behavior is undefined.
namespace std {
class shared_mutex {
public:
// ......
// exclusive ownership
void lock(); // blocking
bool try_lock();
void unlock();
// shared ownership
void lock_shared(); // blocking
bool try_lock_shared();
void unlock_shared();
// ......
};
}
查閱了一下資料,有網(wǎng)友做過(guò)一些優(yōu)先級(jí)相關(guān)的實(shí)驗(yàn),結(jié)果如下(實(shí)驗(yàn)結(jié)果引用自引用地址):
- gcc version 9.3.0的實(shí)現(xiàn)中,shared_mutex是讀優(yōu)先的
- gcc version 10.2.0的實(shí)現(xiàn)中,shared_mutex是寫(xiě)優(yōu)先的