久久精品人人爽,华人av在线,亚洲性视频网站,欧美专区一二三

Go語言互斥鎖Mutex和讀寫鎖RWMutex的用法

217次閱讀
沒有評論

共計 7183 個字符,預計需要花費 18 分鐘才能閱讀完成。

這篇文章主要介紹“Go 語言互斥鎖 Mutex 和讀寫鎖 RWMutex 的用法”,在日常操作中,相信很多人在 Go 語言互斥鎖 Mutex 和讀寫鎖 RWMutex 的用法問題上存在疑惑,丸趣 TV 小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Go 語言互斥鎖 Mutex 和讀寫鎖 RWMutex 的用法”的疑惑有所幫助!接下來,請跟著丸趣 TV 小編一起來學習吧!

sync.Mutex

Go 中使用 sync.Mutex 類型實現 mutex(排他鎖、互斥鎖)。在源代碼的 sync/mutex.go 文件中,有如下定義:

// A Mutex is a mutual exclusion lock. // The zero value for a Mutex is an unlocked mutex. // // A Mutex must not be copied after first use. type Mutex struct {   state int32 sema uint32}

這沒有任何非凡的地方。和 mutex 相關的所有事情都是通過 sync.Mutex 類型的兩個方法 sync.Lock()和 sync.Unlock()函數來完成的,前者用于獲取 sync.Mutex 鎖,后者用于釋放 sync.Mutex 鎖。sync.Mutex 一旦被鎖住,其它的 Lock()操作就無法再獲取它的鎖,只有通過 Unlock()釋放鎖之后才能通過 Lock()繼續獲取鎖。

也就是說,已有的鎖會導致其它申請 Lock()操作的 goroutine 被阻塞,且只有在 Unlock()的時候才會解除阻塞。

另外需要注意,sync.Mutex 不區分讀寫鎖,只有 Lock()與 Lock()之間才會導致阻塞的情況,如果在一個地方 Lock(),在另一個地方不 Lock()而是直接修改或訪問共享數據,這對于 sync.Mutex 類型來說是允許的,因為 mutex 不會和 goroutine 進行關聯。如果想要區分讀、寫鎖,可以使用 sync.RWMutex 類型,見后文。

在 Lock()和 Unlock()之間的代碼段稱為資源的臨界區 (critical section),在這一區間內的代碼是嚴格被 Lock() 保護的,是線程安全的,任何一個時間點都只能有一個 goroutine 執行這段區間的代碼。

以下是使用 sync.Mutex 的一個示例,稍后是非常詳細的分析過程。

package main import (fmt sync time) // 共享變量 var (   m  sync.Mutex    v1 int) // 修改共享變量 // 在 Lock()和 Unlock()之間的代碼部分是臨界區 func change(i int) {   m.Lock()    time.Sleep(time.Second)    v1 = v1 + 1 if v1%10 == 0 {       v1 = v1 – 10*i    }    m.Unlock()} // 訪問共享變量 // 在 Lock()和 Unlock()之間的代碼部分是是臨界區 func read() int {    m.Lock()    a := v1    m.Unlock() return a} func main() { var numGR = 21 var wg sync.WaitGroup    fmt.Printf( %d , read()) // 循環創建 numGR 個 goroutine // 每個 goroutine 都執行 change()、read() // 每個 change()和 read()都會持有鎖 for i := 0; i numGR; i++ {       wg.Add(1) go func(i int) {defer wg.Done()            change(i)            fmt.Printf(– %d , read())        }(i)    }    wg.Wait()}

第一次執行結果:

20 – 1 – 2 – 3 – 4 – 5 – 6 – 7 – 8 – 9 – -100 – -99 – -98 – -97 – -96 – -95 – -94 – -93 – -92 – -91 – -260 – -259

第二次執行結果:注意其中的 -74 和 -72 之間跨了一個數

20 – 1 – 2 – 3 – 4 – 5 – 6 – 7 – 8 – 9 – -80 – -79 – -78 – -77 – -76 – -75 – -74 – -72 – -71 – -230 – -229 – -229

上面的示例中,change()、read()都會申請鎖,并在準備執行完函數時釋放鎖,它們如何修改數據、訪問數據本文不多做解釋。需要詳細解釋的是 main()中的 for 循環部分。

在 for 循環中,會不斷激活新的 goroutine(共 21 個)執行匿名函數,在每個匿名函數中都會執行 change()和 read(),意味著每個 goroutine 都會申請兩次鎖、釋放兩次鎖,且 for 循環中沒有任何 Sleep 延遲,這 21 個 goroutine 幾乎是一瞬間同時激活的。

但由于 change()和 read()中都申請鎖,對于這 21 個 goroutine 將要分別執行的 42 個 critical section,Lock()保證了在某一時間點只有其中一個 goroutine 能訪問其中一個 critical section。當釋放了一個 critical section,其它的 Lock()將爭奪互斥鎖,也就是所謂的競爭現象(race condition)。因為競爭的存在,這 42 個 critical section 被訪問的順序是隨機的,完全無法保證哪個 critical section 先被訪問。

對于前 9 個被調度到的 goroutine,無論是哪個 goroutine 取得這 9 個 change(i)中的 critical section,都只是對共享變量 v1 做加 1 運算,但當第 10 個 goroutine 被調度時,由于 v1 加 1 之后得到 10,它滿足 if 條件,會執行 v1 = v1 – i*10,但這個 i 可能是任意 0 到 numGR 之間的值(因為無法保證并發的 goroutine 的調度順序),這使得 v1 的值從第 10 個 goroutine 開始出現隨機性。但從第 10 到第 19 個 goroutine 被調度的過程中,也只是對共享變量 v1 做加 1 運算,這些值是可以根據第 10 個數推斷出來的,到第 20 個 goroutine,又再次隨機。依此類推。

此外,每個 goroutine 中的 read()也都會參與鎖競爭,所以并不能保證每次 change(i)之后會隨之執行到 read(),可能 goroutine 1 的 change()執行完后,會跳轉到 goroutine 3 的 change()上,這樣一來,goroutine 1 的 read()就無法讀取到 goroutine 1 所修改的 v1 值,而是訪問到其它 goroutine 中修改后的值。所以,前面的第二次執行結果中出現了一次數據跨越。只不過執行完 change()后立即執行 read()的幾率比較大,所以多數時候輸出的數據都是連續的。

總而言之,Mutex 保證了每個 critical section 安全,某一時間點只有一個 goroutine 訪問到這部分,但也因此而出現了隨機性。

如果 Lock()后忘記了 Unlock(),將會永久阻塞而出現死鎖。如果

適合 sync.Mutex 的數據類型

其實,對于內置類型的共享變量來說,使用 sync.Mutex 和 Lock()、Unlock()來保護也是不合理的,因為它們自身不包含 Mutex 屬性。真正合理的共享變量是那些包含 Mutex 屬性的 struct 類型。例如:type mytype struct {

m   sync.Mutex var int }x := new(mytype)

這時只要想保護 var 變量,就先 x.m.Lock(),操作完 var 后,再 x.m.Unlock()。這樣就能保證 x 中的 var 字段變量一定是被保護的。

sync.RWMutex

Go 中使用 sync.RWMutex 類型實現讀寫互斥鎖 rwmutex。在源代碼的 sync/rwmutex.go 文件中,有如下定義:

// A RWMutex is a reader/writer mutual exclusion lock.// The lock can be held by an arbitrary number of readers or a single writer.// The zero value for a RWMutex is an unlocked mutex.//// A RWMutex must not be copied after first use.//// If a goroutine holds a RWMutex for reading and another goroutine might// call Lock, no goroutine should expect to be able to acquire a read lock // until the initial read lock is released. In particular, this prohibits// recursive read locking. This is to ensure that the lock eventually becomes// available; a blocked Lock call excludes new readers from acquiring the// lock. type RWMutex struct {   w Mutex // held if there are pending writers    writerSem   uint32 // 寫鎖需要等待讀鎖釋放的信號量    readerSem   uint32 // 讀鎖需要等待寫鎖釋放的信號量    readerCount int32  // 讀鎖后面掛起了多少個寫鎖申請    readerWait  int32  // 已釋放了多少個讀鎖}

上面的注釋和源代碼說明了幾點:

RWMutex 是基于 Mutex 的,在 Mutex 的基礎之上增加了讀、寫的信號量,并使用了類似引用計數的讀鎖數量
讀鎖與讀鎖兼容,讀鎖與寫鎖互斥,寫鎖與寫鎖互斥,只有在鎖釋放后才可以繼續申請互斥的鎖

可以同時申請多個讀鎖
有讀鎖時申請寫鎖將阻塞,有寫鎖時申請讀鎖將阻塞
只要有寫鎖,后續申請讀鎖和寫鎖都將阻塞

此類型有幾個鎖和解鎖的方法:

func (rw *RWMutex) Lock() func (rw *RWMutex) RLock() func (rw *RWMutex) RLocker() Locker func (rw *RWMutex) RUnlock() func (rw *RWMutex) Unlock()

其中:

Lock()和 Unlock()用于申請和釋放寫鎖
RLock() 和 RUnlock()用于申請和釋放讀鎖一次 RUnlock()操作只是對讀鎖數量減 1,即減少一次讀鎖的引用計數
如果不存在寫鎖,則 Unlock()引發 panic,如果不存在讀鎖,則 RUnlock()引發 panic
RLocker()用于返回一個實現了 Lock()和 Unlock()方法的 Locker 接口

此外,無論是 Mutex 還是 RWMutex 都不會和 goroutine 進行關聯,這意味著它們的鎖申請行為可以在一個 goroutine 中操作,釋放鎖行為可以在另一個 goroutine 中操作。

由于 RLock()和 Lock()都能保證數據不被其它 goroutine 修改,所以在 RLock()與 RUnlock()之間的,以及 Lock()與 Unlock()之間的代碼區都是 critical section。

以下是一個示例,此示例中同時使用了 Mutex 和 RWMutex,RWMutex 用于讀、寫,Mutex 只用于讀。

package main import (fmt os sync time) var Password = secret{password: myPassword}type secret struct {RWM sync.RWMutex M sync.Mutex password string} // 通過 rwmutex 寫 func Change(c *secret, pass string) {c.RWM.Lock()    fmt.Println(Change with rwmutex lock)    time.Sleep(3 * time.Second) c.password = pass c.RWM.Unlock()} // 通過 rwmutex 讀 func rwMutexShow(c *secret) string {c.RWM.RLock()    fmt.Println(show with rwmutex ,time.Now().Second())    time.Sleep(1 * time.Second) defer c.RWM.RUnlock() return c.password} // 通過 mutex 讀,和 rwMutexShow 的唯一區別在于鎖的方式不同 func mutexShow(c *secret) string {c.M.Lock()    fmt.Println(show with mutex: ,time.Now().Second())    time.Sleep(1 * time.Second) defer c.M.Unlock() return c.password} func main() { // 定義一個稍后用于覆蓋 (重寫) 的函數 var show = func(c *secret) string {return} // 通過變量賦值的方式,選擇并重寫 showFunc 函數 if len(os.Args) != 2 {       fmt.Println( Using sync.RWMutex! ,time.Now().Second())        show = rwMutexShow    } else {       fmt.Println( Using sync.Mutex! ,time.Now().Second())        show = mutexShow    } var wg sync.WaitGroup // 激活 5 個 goroutine,每個 goroutine 都查看 // 根據選擇的函數不同,showFunc()加鎖的方式不同 for i := 0; i i++ {        wg.Add(1)        go func() { defer wg.Done()            fmt.Println(Go Pass: , show( Password),time.Now().Second())        }()    } // 激活一個申請寫鎖的 goroutine go func() {        wg.Add(1) defer wg.Done() Change( Password, 123456)    }() // 阻塞,直到所有 wg.Done wg.Wait()}

Change()函數申請寫鎖,并睡眠 3 秒后修改數據,然后釋放寫鎖。

rwMutexShow()函數申請讀鎖,并睡眠一秒后取得數據,并釋放讀鎖。注意,rwMutexShow()中的 print 和 return 是相隔一秒鐘的。

mutexShow()函數申請 Mutex 鎖,和 RWMutex 互不相干。和 rwMutexShow()唯一不同之處在于申請的鎖不同。

main()中,先根據命令行參數數量決定運行哪一個 show()。之所以能根據函數變量來賦值,是因為先定義了一個 show()函數,它的函數簽名和 rwMutexShow()、mutexShow()的簽名相同,所以可以相互賦值。

for 循環中激活了 5 個 goroutine 并發運行,for 瞬間激活 5 個 goroutine 后,繼續執行 main()代碼會激活另一個用于申請寫鎖的 goroutine。這 6 個 goroutine 的執行順序是隨機的。

如果 show 選中的函數是 rwMutexShow(),則 5 個 goroutine 要申請的 RLock()鎖和寫鎖是沖突的,但 5 個 RLock()是兼容的。所以,只要某個時間點調度到了寫鎖的 goroutine,剩下的讀鎖 goroutine 都會從那時開始阻塞 3 秒。

除此之外,還有一個不嚴格準確,但在時間持續長短的理論上來說能保證的一個規律:當修改數據結束后,各個剩下的 goroutine 都申請讀鎖,因為申請后立即 print 輸出,然后睡眠 1 秒,但 1 秒時間足夠所有剩下的 goroutine 申請完讀鎖,使得 show with rwmutex 輸出是連在一起,輸出的 Go Pass: 123456 又是連在一起的。

某次結果如下:

Using sync.RWMutex! 58 show with rwmutex 58 Change with rwmutex lock Go Pass: myPassword 59 show with rwmutex 2 show with rwmutex 2 show with rwmutex 2 show with rwmutex 2 Go Pass: 123456 3 Go Pass: 123456 3 Go Pass: 123456 3 Go Pass: 123456 3

如果 show 選中的函數是 mutexShow(),則讀數據和寫數據互不沖突,但讀和讀是沖突的 (因為 Mutex 的 Lock() 是互斥的)。

某次結果如下:

Using sync.Mutex! 30 Change with rwmutex lock show with mutex: 30 Go Pass: myPassword 31 show with mutex: 31 Go Pass: myPassword 32 show with mutex: 32 Go Pass: 123456 33 show with mutex: 33 show with mutex: 34 Go Pass: 123456 34 Go Pass: 123456 35

用 Mutex 還是用 RWMutex

Mutex 和 RWMutex 都不關聯 goroutine,但 RWMutex 顯然更適用于讀多寫少的場景。僅針對讀的性能來說,RWMutex 要高于 Mutex,因為 rwmutex 的多個讀可以并存。

到此,關于“Go 語言互斥鎖 Mutex 和讀寫鎖 RWMutex 的用法”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注丸趣 TV 網站,丸趣 TV 小編會繼續努力為大家帶來更多實用的文章!

正文完
 
丸趣
版權聲明:本站原創文章,由 丸趣 2023-08-01發表,共計7183字。
轉載說明:除特殊說明外本站除技術相關以外文章皆由網絡搜集發布,轉載請注明出處。
評論(沒有評論)
主站蜘蛛池模板: 井陉县| 和硕县| 苍南县| 从化市| 周至县| 蓝山县| 洛南县| 大埔区| 绩溪县| 镇坪县| 长葛市| 凭祥市| 漳州市| 娱乐| 长沙市| 财经| 胶州市| 茂名市| 博野县| 乐业县| 灵寿县| 陆良县| 镇坪县| 沙雅县| 临西县| 临城县| 铁力市| 三江| 邮箱| 江油市| 石河子市| 始兴县| 安阳市| 河源市| 台州市| 阿合奇县| 汤阴县| 阿鲁科尔沁旗| 庆城县| 句容市| 类乌齐县|