欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

從源碼深入理解golang?RWMutex讀寫鎖操作

 更新時間:2023年05月05日 11:15:12   作者:JonPan  
這篇文章主要介紹了從源碼深入理解golang?RWMutex讀寫鎖操作,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下

環(huán)境:go 1.19.8

在讀多寫少的情況下,即使一段時間內(nèi)沒有寫操作,大量并發(fā)的讀訪問也不得不在Mutex的保護下變成串行訪問,這種情況下,使用Mutex,對性能影響比較大。
所以就要區(qū)分讀寫操作。如果某個讀操作的g持有了鎖,其他讀操作的g就不必等待了,可以并發(fā)的訪問共享變量,這樣就可以將串行的讀變成并行的讀,提高讀操作的性能??衫斫鉃楣蚕礞i。

當寫操作的g持有鎖,它是一個排他鎖,不管其他的g是寫操作還是讀操作,都需要阻塞等待持有鎖的g釋放鎖。

什么是RWMutex?

reader/writer互斥鎖,在某一時刻只能由任意數(shù)量的reader持有,或者是只被單個writer持有。
RWMutex實現(xiàn)了5個方法:

  • Lock/Unlock:寫操作時調(diào)用。如果鎖已經(jīng)被reader或者writer持有,那么,Lock方法會一直阻塞,直到能獲取到鎖;Unlock是對應的釋放鎖方法
  • RLock/RUnlock:讀操作時調(diào)用。如果鎖已經(jīng)被writer持有,RLock方法會一直阻塞,直到能獲取鎖,否則直接return;Rnlock是對應的釋放鎖方法
  • RLocker:這個方法的作用是為讀操作返回一個 Locker 接口的對象

案例:計數(shù)器,1writer n reader

使用場景

如果可以明確區(qū)分 reader 和 writer goroutine ,且有大量的并發(fā)讀,少量的并發(fā)寫,并且有強烈的性能要求,可以考慮使用讀寫鎖RWMutex替換Mutex

實現(xiàn)原理

RWMutex 是很常見的并發(fā)原語,很多編程語言的庫都提供了類似的并發(fā)類型。RWMutex
一般都是基于互斥鎖、條件變量(condition variables)或者信號量(semaphores)等
并發(fā)原語來實現(xiàn)。Go 標準庫中的 RWMutex 是基于 Mutex 實現(xiàn)的。
reader-writers 問題,一般有三類,基于對讀和寫操作的優(yōu)先級,讀寫鎖的設計和實現(xiàn)也分成三類

  • Read-Preferring:讀優(yōu)先的設計可以提供很高的并發(fā)性。但在競爭激烈的情況下會導致寫?zhàn)囸I
  • Write-Preferring:如果有一個writer在等待請求鎖,它會阻止新來請求鎖reader獲取到鎖,優(yōu)先保障writer。當然,如果reader已經(jīng)獲得鎖,新請求的writer也需要等待已持有鎖的reader釋放鎖。寫優(yōu)先級設計中的優(yōu)先權(quán)是針對新來的請求而言的。這種設計主要避免了 writer 的饑餓問題。
  • 不指定優(yōu)先級:這種設計比較簡單,不區(qū)分 reader 和 writer 優(yōu)先級,某些場景下這種不指定優(yōu)先級的設計反而更有效,因為第一類優(yōu)先級會導致寫?zhàn)囸I,第二類優(yōu)先級可能會導致讀饑餓,這種不指定優(yōu)先級的訪問不再區(qū)分讀寫,大家都是同一個優(yōu)先級,解決了饑餓的問題。

Go 標準庫中的 RWMutex 設計是 Write-preferring 方案。一個正在阻塞的 Lock 調(diào)用
會排除新的 reader 請求到鎖。

源碼解析

上鎖解鎖流程以及數(shù)值變化情況

rwmutexMaxReaders 的數(shù)量被初始化為1<<30,理想中,寫鎖不會持續(xù)很久,不會導致readerCount 自動從負值自動+1回到正值。

RLock/RUnlock實現(xiàn)

type RWMutex struct {
	w           sync.Mutex // hold if there are pending writers
	writerSem   uint32     // 寫 阻塞信號
	readerSem   uint32     // 讀 阻塞信號
	readerCount int32      // 正在讀的調(diào)用者數(shù)量/ 當為負數(shù)時 表示有write持有鎖
	readerWait  int32      // writer持有鎖之前正等待解鎖的數(shù)量
}
const rwmutexMaxReaders = 1 << 30
func (rw *RWMutex) RLock() {
	if atomic.AddInt32(&rw.readerCount, 1) < 0 {
		// 寫端 持有鎖, 讀端阻塞
		runtime_SemacquireMutex(&rw.readerSem, false, 0)
	}
}
func (rw *RWMutex) RUnlock() {
	if r := atomic.AddInt32(&rw.readerCount, -1); r < 0 {
		rw.rUnlockSlow(r)
	}
}
func (rw *RWMutex) rUnlockSlow(r int32) {
	if r+1 == 0 || r+1 == -rwmutexMaxReaders {
		fatal("sync: RUnlock of unlocked RWMutex")
	}
	if atomic.AddInt32(&rw.readerWait, -1) == 0 {
		// 無讀者等待,喚醒寫端等待者
		runtime_Semrelease(&rw.writerSem, false, 1)
	}
}

RLock

第11行,上讀鎖,首先對readerCount進行原子加1,如果小于0則表示存在寫鎖,直接阻塞。為什么readerCount會存在負值?這個要看readerCount除了在RLock中處理,還在哪里被處理了。可以看到在獲取寫鎖時有響應代碼。后面在解釋。如果原子加大于等于0,則表示獲取讀鎖成功。

RUnlock

第18行,讀解鎖,對readerCount進行原子減1,如果小于零,則表示存在活躍的reader(即當前獲得互斥鎖的寫鎖之前獲取到讀鎖權(quán)限的讀者數(shù)量),readerWait 字段就減 1,直到所有的活躍的 reader 都釋放了讀鎖,才會喚醒這個 write

Lock/Unlock

func (rw *RWMutex) Lock() {
	// 1. 先嘗試獲取互斥鎖
	rw.w.Lock()
	// 2. 看是否有其他正持有鎖的讀者,有則阻塞
	r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders
	if r != 0 && atomic.AddInt32(&rw.readerWait, r) != 0 {
		// rc - rwmutexMaxReaders + rwmutexMaxReaders > 0說明還有等待者, 寫端阻塞
		runtime_SemacquireMutex(&rw.writerSem, false, 0)
	}
}
func (rw *rwMutex) Unlock() {
	r := atomic.AddInt32(&rw.readerCount, rwmutexMaxReaders)
	if r >= rwmutexMaxReaders {
		fatal("sync: Unlock of unlocked RWMutex")
	}
	// 如果有等待的讀者,先喚醒
	for i := 0; i < int(r); i++ {
		runtime_Semrelease(&rw.readerSem, false, 0)
	}
	// 釋放互斥鎖
	rw.w.Unlock()
}

Lock

  • 先獲取互斥鎖
  • 成功獲取后,r=readerCount-rwmutexMaxReaders,得到的數(shù)值就是一個負數(shù),在加上rwmutexReaders就表示寫鎖等待者的數(shù)量,此時,如果r不等于0,且readerWait+r!=0,則表示有讀等待者,寫鎖阻塞

我們知道,寫操作要等待讀操作結(jié)束后才可以獲得鎖,寫操作等待期間可能還有新的讀操作持續(xù)到來,如果寫操作等待所有讀操作結(jié)束,就會出現(xiàn)饑餓現(xiàn)象。然而,通過readerWait可完美解決這個問題。

寫操作到來時,會把readerCount值拷貝到readerWait中,用于標記排在寫操作之前到讀者個數(shù)。
當讀操作結(jié)束后,除了會遞減readerCount,還會遞減readerWait的值,當readerWait值變?yōu)?時會喚醒寫操作。

寫操作之后產(chǎn)生的讀操作會加入到readerCount中,阻塞知道寫鎖釋放。

Unlock

上面說過,寫鎖之后來的讀者會被阻塞,所以在寫鎖釋放之際,會看是否有需要喚醒的讀者,再釋放互斥鎖

場景討論

寫操作如何阻塞寫操作

讀寫鎖包含一個互斥鎖(Mutex),寫鎖必須先獲取該互斥鎖,如果互斥鎖已被協(xié)程A獲取,意味者其他協(xié)程只能阻塞等待互斥鎖釋放

寫操作是如何阻塞讀操作

readerCount是個整型值,用于表示讀者數(shù)量,不考慮寫操作的情況下,每次獲取讀鎖,將該值加1,每次解鎖將其減1,所以readerCount的取值為[0, N],最大可支持2^30個并發(fā)讀者。

當寫鎖定進行時,會先將readerCount -= rwmutextMaxReaders(2^30),此時 readerCount負數(shù)。這時再有讀者到了,檢測到readerCount為負值,則表示有寫操作正在進行,后來到讀者阻塞等待。等待者的數(shù)量即 reaerCount + 2^30

讀操作是如何阻止寫操作的

寫操作時,會把readerCount的值拷貝到readerWait中,用于標記在寫操作前面讀者的個數(shù),前面的寫鎖釋放后,會遞減readerCount,readerWait,當readerWait值變?yōu)?時喚醒寫操作

3個踩坑點

不可復制

rwmutex是由一個互斥鎖和四個輔助字段組成的,與互斥鎖一樣,讀寫鎖也是不能復制的。
一旦讀寫鎖被使用,它的字段就會記錄它當前的一些狀態(tài),如果此時去復制這把鎖,就會把它的狀態(tài)也復制過去。但原來的鎖在釋放的時候,并不會修改復制出來的讀寫鎖,會導致復制出來的讀寫鎖狀態(tài)異常,可能永遠無法釋放鎖。

重入導致死鎖

讀寫鎖重入,或者遞歸調(diào)用,導致的死鎖情況很多

讀寫鎖內(nèi)部基于互斥鎖實現(xiàn)對writer并發(fā)控制,而互斥鎖本身就有重入問題,所以,writer重入調(diào)用Lock,會導致死鎖

func foo(l *sync.RWMutex) {
    fmt.Println("lock in foo")
    l.Lock()
    bar(l)
    l.Unlock()
}
func bar(l *sync.RWMutex) {
    fmt.Println("lock in bar")
    l.Lock()
    l.Unlock()
}
func main() {
    l := &sync.RWMutex{}
    foo(l)
}

2.當一個 writer 請求鎖的時候,如果已經(jīng)有一些活躍的 reader,它會等待這些活躍的reader 完成,才有可能獲取到鎖,但是,如果之后活躍的 reader 再依賴新的 reader 的話,這些新的 reader 就會等待 writer 釋放鎖之后才能繼續(xù)執(zhí)行,這就形成了一個環(huán)形依賴: writer 依賴活躍的 reader -> 活躍的 reader 依賴新來的 reader -> 新來的 reader依賴 writer。

func main() {
    var mu sync.RWMutex
    go func() {
        time.Sleep(200*time.Millisecond)
        mu.Lock()
        fmt.Println("Lock")
        time.Sleep(100*time.Millisend)
        mu.Unlock()
        fmt.Println("Unlock")
    }
    go func() {
        factorial(&mu, 10) // 計算10的階乘
    }
    select {}
}
// 
func factorial(m *sync.RWMutex, n int) {
    if n < 1 {
        return 0
    }
    fmt.Println("RLock")
    m.RLock()
    defer func() {
        fmt.Println("RUnlock")
        m.RUnlock()
    }
    time.Sleep(100*time.Millisecond)
    return factorial(m, n-1) * n
}

factorial 方法是一個遞歸計算階乘的方法,我們用它來模擬 reader。為了更容易地制造出死鎖場景,在這里加上了 sleep 的調(diào)用,延緩邏輯的執(zhí)行。這個方法會調(diào)用讀鎖(第 27
行),在第 33 行遞歸地調(diào)用此方法,每次調(diào)用都會產(chǎn)生一次讀鎖的調(diào)用,所以可以不斷地產(chǎn)生讀鎖的調(diào)用,而且必須等到新請求的讀鎖釋放,這個讀鎖才能釋放。同時,我們使用另一個 goroutine 去調(diào)用 Lock 方法,來實現(xiàn) writer,這個 writer 會等待200 毫秒后才會調(diào)用 Lock,這樣在調(diào)用 Lock 的時候,factoria 方法還在執(zhí)行中不斷調(diào)用
RLock。這兩個 goroutine 互相持有鎖并等待,誰也不會退讓一步,滿足了“writer 依賴活躍的reader -> 活躍的 reader 依賴新來的 reader -> 新來的 reader 依賴 writer”的死鎖條件,所以就導致了死鎖的產(chǎn)生。

釋放未加鎖的RWMutex

鎖都是成對出現(xiàn)的,Lock和RLock的多余調(diào)用會導致鎖沒有被釋放,可能會出現(xiàn)死鎖。
而Unlock和RUnlock多余調(diào)用會導致panic

參考

go中sync.RWMutex源碼解讀

到此這篇關(guān)于從源碼深入理解golang RWMutex讀寫鎖操作的文章就介紹到這了,更多相關(guān)go讀寫鎖RWMutex內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • go語言單元測試基準測試及表驅(qū)動測試示例詳解

    go語言單元測試基準測試及表驅(qū)動測試示例詳解

    這篇文章主要為大家介紹了go語言單元測試基準測試及表驅(qū)動測試示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-08-08
  • Go語言中g(shù)o?mod?vendor使用方法

    Go語言中g(shù)o?mod?vendor使用方法

    go mod vendor的功能是將新增的依賴包自動寫入當前項目的 vendor目錄,下面這篇文章主要給大家介紹了關(guān)于Go語言中g(shù)o?mod?vendor使用的相關(guān)資料,需要的朋友可以參考下
    2022-10-10
  • golang?中?recover()的使用方法

    golang?中?recover()的使用方法

    這篇文章主要介紹了Guam與golang??recover()的使用方法,Recover?是一個Go語言的內(nèi)建函數(shù),可以讓進入宕機流程中的?goroutine?恢復過來,下文更多相關(guān)資料需要的小伙伴可以參考一下
    2022-04-04
  • Golang兩行代碼實現(xiàn)發(fā)送釘釘機器人消息

    Golang兩行代碼實現(xiàn)發(fā)送釘釘機器人消息

    創(chuàng)建一個釘釘機器人必須使用加簽,本文通過Golang兩行代碼實現(xiàn)發(fā)送釘釘機器人消息,本文給大家介紹的非常詳細,感興趣的朋友跟隨小編一起看看吧
    2021-12-12
  • go-cqhttp智能聊天功能的實現(xiàn)

    go-cqhttp智能聊天功能的實現(xiàn)

    這篇文章主要介紹了go-cqhttp智能聊天功能,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-09-09
  • go?gin?正確讀取http?response?body內(nèi)容并多次使用詳解

    go?gin?正確讀取http?response?body內(nèi)容并多次使用詳解

    這篇文章主要為大家介紹了go?gin?正確讀取http?response?body內(nèi)容并多次使用解決思路,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-01-01
  • Go-客戶信息關(guān)系系統(tǒng)的實現(xiàn)

    Go-客戶信息關(guān)系系統(tǒng)的實現(xiàn)

    這篇文章主要介紹了Go-客戶信息關(guān)系系統(tǒng)的實現(xiàn),本文章內(nèi)容詳細,具有很好的參考價值,希望對大家有所幫助,需要的朋友可以參考下
    2023-01-01
  • Go?Java算法之單詞搜索示例詳解

    Go?Java算法之單詞搜索示例詳解

    這篇文章主要為大家介紹了Go?Java算法之單詞搜索示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-08-08
  • 在?Go?語言中使用?regexp?包處理正則表達式的操作

    在?Go?語言中使用?regexp?包處理正則表達式的操作

    正則表達式是處理字符串時一個非常強大的工具,而?Go?語言的?regexp?包提供了簡單而強大的接口來使用正則表達式,本文將介紹如何在?Go?中使用?regexp?包來編譯和執(zhí)行正則表達式,以及如何從文本中匹配和提取信息,感興趣的朋友一起看看吧
    2023-12-12
  • GoLang基礎(chǔ)學習之go?test測試

    GoLang基礎(chǔ)學習之go?test測試

    相信每位編程開發(fā)者們應該都知道,Golang作為一門標榜工程化的語言,提供了非常簡便、實用的編寫單元測試的能力,下面這篇文章主要給大家介紹了關(guān)于GoLang基礎(chǔ)學習之go?test測試的相關(guān)資料,需要的朋友可以參考下
    2022-08-08

最新評論