剖析Go的讀寫鎖

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

源碼級剖析Go標準庫中的sync.RWMutex。

概述

RWMutex,讀寫鎖,又稱“讀寫互斥鎖”。
讀寫鎖簡單來說就是可以由任意數量的讀者同時使用,或者只由一個寫者使用的鎖。

讀寫鎖和互斥量(Mutex)類似,但是比起互斥量有著更高的並行性,它允許多個讀者同時讀取,因此有一些特殊的應用情境。
在並發編程的很多情境下,資料的讀取可能比寫入更加頻繁,這時就要允許多個線程同時讀取一塊內容。

用例

Go中,RWMutex的零值是一個未加鎖的互斥量。

RWMutex使用起來相對比較簡單,這裡舉一個簡單的例子:

package mainimport ("fmt""sync""time")func main() {rw := new(sync.RWMutex)for i := 0; i < 2; i++ {   // 建立兩個寫者go func() {for j := 0; j < 3; j++ {rw.Lock()// 寫rw.Unlock()}}()}for i := 0; i < 5; i++ {    // 建立兩個讀者go func() {for j := 0; j < 3; j++ {rw.RLock()// 讀rw.RUnlock()}}()}time.Sleep(time.Second)fmt.Println("Done")}

PlayGround

一個(神奇)優秀的(大坑)特性

讀者在讀的時候,不能夠假定別的讀者也能夠獲得鎖。因此,禁止讀鎖嵌套。

是不是有點兒繞?下面舉個“七秒例”:?

  • 第一秒:讀者1在第1秒成功申請了讀鎖
  • 第二秒:寫者1在第2秒申請寫鎖,申請失敗,阻塞,但它會防止新的讀者獲鎖
  • 第三秒:讀者2在第3秒申請讀鎖,申請失敗
  • 第四秒:讀者1釋放讀鎖,寫者1獲得寫鎖
  • 第五秒:寫者1釋放寫鎖,讀者2獲得讀鎖
  • 第六秒:讀者1再次申請讀鎖,申請成功,與讀者2共用
  • 第七秒:讀者1、讀者2釋放讀鎖,結束

當寫鎖阻塞時,新的讀鎖是無法申請的,這可以有效防止寫者饑餓。如果一個線程因為某種原因,導致得不到CPU已耗用時間,這種狀態被稱之為 饑餓

然而,這種機制也禁止了讀鎖嵌套。讀鎖嵌套可能造成死結:

package mainimport ("fmt""sync""time")func main() {rw := new(sync.RWMutex)var deadLockCase time.Duration = 1go func() {time.Sleep(time.Second * deadLockCase)fmt.Println("Writer Try")rw.Lock()fmt.Println("Writer Fetch")time.Sleep(time.Second * 1)fmt.Println("Writer Release")rw.Unlock()}()fmt.Println("Reader 1 Try")rw.RLock()fmt.Println("Reader 1 Fetch")time.Sleep(time.Second * 2)fmt.Println("Reader 2 Try")rw.RLock()fmt.Println("Reader 2 Fetch")time.Sleep(time.Second * 2)fmt.Println("Reader 1 Release")rw.RUnlock()time.Sleep(time.Second * 1)fmt.Println("Reader 2 Release")rw.RUnlock()time.Sleep(time.Second * 2)fmt.Println("Done")}

讀者1和讀者2是嵌套關係,按照這種時間安排,上述程式會導致死結。

而有些死結的可怕之處就在於,它不一定會發生。假設上面程式中的time.Sleep都是隨機的時間,那麼這一段代碼每次的結果有可能不一致,這會給Debug帶來極大的困難。

吾聞讀鎖莫嵌套,寫鎖嵌套長已矣。(讀鎖嵌套了還有機率成功,寫鎖嵌套了100%完蛋?)

源碼剖析

(源碼具體內容、行數,以版本go version 1.8.1為例。)

為了方便理解,可以把所有的if race.Enabled {...}扔掉不看。接下來,我們重述“七秒例”。?

第一秒,讀者1請求讀鎖。

Line41: if atomic.AddInt32(&rw.readerCount, 1) < 0 {// A writer is pending, wait for it.runtime_Semacquire(&rw.readerSem)}

讀者數量readerCount開始是0,這個時候加1,變成了1,不符合判負條件所以跳出,成功獲得讀鎖一枚。

第二秒,寫者嘗試擷取寫鎖。第85行擷取w的鎖。不管這個讀寫鎖有沒有擷取成功,先排斥別的寫者。

Line85:// First, resolve competition with other writers.rw.w.Lock()// Announce to readers there is a pending writer.r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders// Wait for active readers.if r != 0 && atomic.AddInt32(&rw.readerWait, r) != 0 {runtime_Semacquire(&rw.writerSem)}

剛才說了,一個寫者阻塞在這裡的時候,也不會讓新的讀者去讀了,所以它幹了一件非常壞的事情:
把readerCount變成了1-rwmutexMaxReaders。
這樣就能卡住新來的讀者了。
接下來,算出r等於1。這意味著有當前有寫者存在。
因為有讀者,所以寫者卡在了訊號量writerSem上。但是它不甘心啊,心想“等完現在的這幾個讀者,我就要去寫!”,它默默地把現在佔有讀鎖的人記在了小本本rw.readerWait上。在本例子中,readerWait被設定為了1。

第三秒,讀者2嘗試獲得讀鎖,它又來到了第41行,結果發現讀者的數量是1-rwmutexMaxReaders,好吧,它只好卡在訊號量readerSem上。

第四秒,讀者1調用RUnlock(),它首先把讀者數量減一,畢竟自己已經不讀了。

Line61:if r := atomic.AddInt32(&rw.readerCount, -1); r < 0 {// A writer is pending.if atomic.AddInt32(&rw.readerWait, -1) == 0 {// The last reader unblocks the writer.runtime_Semrelease(&rw.writerSem)}}

在讀者數量減一的時候,它發現讀者數量是負數,這回讀者1明白了,有一個寫者在等待寫。估計讀者1自己已經在這個寫者的小本本readerWait上了,因此它把readerWait減一,表示自己不讀了。這時候讀者1發現自己就是最後一個讀者了,所以趕緊祭出writerSem,讓寫者可以去寫。
讀者1釋放了writerSem訊號量以後,寫者很快就收到了這個提醒,興高采烈地獲得了寫鎖,開始自己的寫作生涯。

讀者2還卡著呢…

第五秒,寫者1寫完了一稿便不想寫了,調用Unlock()準備釋放讀鎖。

Line114:// Announce to readers there is no active writer.r := atomic.AddInt32(&rw.readerCount, rwmutexMaxReaders)// Unblock blocked readers, if any.for i := 0; i < int(r); i++ {runtime_Semrelease(&rw.readerSem)}

只見他重新為readerCount加上rwmutexMaxReaders,使他重新變為了正數。這個正數恰好也是阻塞的讀者的數量。
接下來,寫者按照這個讀者的數量,釋放了這麼多的readerSem訊號量,相當於將所有阻塞的讀者一一喚醒。讀者2在收到readerSem的那一刻喜極而泣,它終於可以讀了。

第六秒,讀者1又來了,它把讀者數量加1,發現它是正數哎,寫者現在又沒來,它再次幸運地瞬間獲得讀鎖,與讀者2一起讀了起來。

第七秒,讀者1和讀者2都釋放了自己的讀鎖。至此,結束。

名詞解釋

中文 英文 解釋
訊號量 (也稱號誌) Semaphore
條件變數 Condition
互斥量 Mutex

參考文獻

  1. Wikipedia: Semaphore (programming))

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.