中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Golang中的RWMutex怎么使用

發布時間:2023-05-09 16:04:57 來源:億速云 閱讀:220 作者:iii 欄目:開發技術

本篇內容主要講解“Golang中的RWMutex怎么使用”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“Golang中的RWMutex怎么使用”吧!

    RWMutex 的整體模型

    正如 RWMutex 的命名那樣,它是區分了讀鎖和寫鎖的鎖,所以我們可以從讀和寫兩個方面來看 RWMutex 的模型。

    下文中的 reader 指的是進行讀操作的 goroutine,writer 指的是進行寫操作的 goroutine。

    讀操作模型

    我們可以用下圖來表示 RWMutex 的讀操作模型:

    Golang中的RWMutex怎么使用

    上圖使用了 w.Lock,是因為 RWMutex 的實現中,寫鎖是使用 Mutex 來實現的。

    說明:

    • 讀操作的時候可以同時有多個 goroutine 持有 RLock,然后進入臨界區。(也就是可以并行讀),上圖的 G1G2G3 就是同時持有 RLock 的幾個 goroutine。

    • 在讀操作的時候,如果有 goroutine 持有 RLock,那么其他 goroutine (不管是讀還是寫)就只能等待,直到所有持有 RLock 的 goroutine 釋放鎖。

    • 也就是上圖的 G4 需要等待 G1G2G3 釋放鎖之后才能進入臨界區。

    • 最后,因為 G5G6 這兩個協程獲取鎖的時機比 G4 晚,所以它們會在 G4 釋放鎖之后才能進入臨界區。

    寫操作模型

    我們可以用下圖來表示 RWMutex 的寫操作模型:

    Golang中的RWMutex怎么使用

    說明:

    寫操作的時候只能有一個 goroutine 持有 Lock,然后進入臨界區,釋放寫鎖之前,所有其他的 goroutine 都只能等待。

    上圖的 G1~G5 表示的是按時間順序先后獲取鎖的幾個 goroutine。

    上面幾個 goroutine 獲取鎖的過程是:

    • G1 獲取寫鎖,進入臨界區。然后 G2G3G4G5 都在等待。

    • G1 釋放寫鎖之后,G2G3 可以同時獲取讀鎖,進入臨界區。然后 G3G4G5 都在等待。

    • G2G3 可以同時獲取讀鎖,進入臨界區。然后 G4G5 都在等待。

    • G2G3 釋放讀鎖之后,G4 獲取寫鎖,進入臨界區。然后 G5 在等待。

    • 最后,G4 釋放寫鎖,G5 獲取讀鎖,進入臨界區。

    基本用法

    RWMutex 中包含了以下的方法:

    • Lock:獲取寫鎖,如果有其他 goroutine 持有讀鎖或寫鎖,那么就會阻塞等待。

    • Unlock:釋放寫鎖。

    • RLock:獲取讀鎖,如果有其他 goroutine 持有寫鎖,那么就會阻塞等待。

    • RUnlock:釋放讀鎖。

    其他不常用的方法:

    • RLocker:返回一個讀鎖,該鎖包含了 RLockRUnlock 方法,可以用來獲取讀鎖和釋放讀鎖。

    • TryLock: 嘗試獲取寫鎖,如果獲取成功,返回 true,否則返回 false。不會阻塞等待。

    • TryRLock: 嘗試獲取讀鎖,如果獲取成功,返回 true,否則返回 false。不會阻塞等待。

    一個簡單的例子

    我們可以通過下面的例子來看一下 RWMutex 的基本用法:

    package mutex
    
    import (
       "sync"
       "testing"
    )
    
    var config map[string]string
    var mu sync.RWMutex
    
    func TestRWMutex(t *testing.T) {
       config = make(map[string]string)
    
       // 啟動 10 個 goroutine 來寫
       var wg1 sync.WaitGroup
       wg1.Add(10)
       for i := 0; i < 10; i++ {
          go func() {
             set("foo", "bar")
             wg1.Done()
          }()
       }
    
       // 啟動 100 個 goroutine 來讀
       var wg2 sync.WaitGroup
       wg2.Add(100)
       for i := 0; i < 100; i++ {
          go func() {
             get("foo")
             wg2.Done()
          }()
       }
    
       wg1.Wait()
       wg2.Wait()
    }
    
    // 獲取配置
    func get(key string) string {
       // 獲取讀鎖,可以多個 goroutine 并發讀取
       mu.RLock()
       defer mu.RUnlock()
    
       if v, ok := config[key]; ok {
          return v
       }
    
       return ""
    }
    
    // 設置配置
    func set(key, val string) {
       // 獲取寫鎖
       mu.Lock()
       defer mu.Unlock()
    
       config[key] = val
    }

    上面的例子中,我們啟動了 10 個 goroutine 來寫配置,啟動了 100 個 goroutine 來讀配置。 這跟我們現實開發中的場景是一樣的,很多時候其實是讀多寫少的。 如果我們在讀的時候也使用互斥鎖,那么就會導致讀的性能非常差,因為讀操作一般都不會有副作用的,但是如果使用互斥鎖,那么就只能一個一個的讀了。

    而如果我們使用 RWMutex,那么就可以同時有多個 goroutine 來讀取配置,這樣就可以大大提高讀的性能。 因為我們進行讀操作的時候,可以多個 goroutine 并發讀取,這樣就可以大大提高讀的性能。

    RWMutex 使用的注意事項

    在《深入理解 go Mutex》中,我們已經講過了 Mutex 的使用注意事項, 其實 RWMutex 的使用注意事項也是差不多的:

    • 不要忘記釋放鎖,不管是讀鎖還是寫鎖。

    • Lock 之后,沒有釋放鎖之前,不能再次使用 Lock

    • Unlock 之前,必須已經調用了 Lock,否則會 panic

    • 在第一次使用 RWMutex 之后,不能復制,因為這樣一來 RWMutex 的狀態也會被復制。這個可以使用 go vet 來檢查。

    源碼剖析

    RWMutex 的一些實現原理跟 Mutex 是一樣的,比如阻塞的時候使用信號量等,在 Mutex 那一篇中已經有講解了,這里不再贅述。 這里就 RWMutex 的實現原理進行一些簡單的剖析。

    RWMutex 結構體

    RWMutex 的結構體定義如下:

    type RWMutex struct {
       w           Mutex        // 互斥鎖,用于保護讀寫鎖的狀態
       writerSem   uint32       // writer 信號量
       readerSem   uint32       // reader 信號量
       readerCount atomic.Int32 // 所有 reader 數量
       readerWait  atomic.Int32 // writer 等待完成的 reader 數量
    }

    各字段含義:

    • w:互斥鎖,用于保護讀寫鎖的狀態。RWMutex 的寫鎖是互斥鎖,所以直接使用 Mutex 就可以了。

    • writerSem:writer 信號量,用于實現寫鎖的阻塞等待。

    • readerSem:reader 信號量,用于實現讀鎖的阻塞等待。

    • readerCount:所有 reader 數量(包括已經獲取讀鎖的和正在等待獲取讀鎖的 reader)。

    • readerWait:writer 等待完成的 reader 數量(也就是獲取寫鎖的時刻,已經獲取到讀鎖的 reader 數量)。

    因為要區分讀鎖和寫鎖,所以在 RWMutex 中,我們需要兩個信號量,一個用于實現寫鎖的阻塞等待,一個用于實現讀鎖的阻塞等待。 我們需要特別注意的是 readerCountreaderWait 這兩個字段,我們可能會比較好奇,為什么有了 readerCount 這個字段, 還需要 readerWait 這個字段呢?

    這是因為,我們在嘗試獲取寫鎖的時候,可能會有多個 reader 正在使用讀鎖,這時候我們需要知道有多少個 reader 正在使用讀鎖, 等待這些 reader 釋放讀鎖之后,就獲取寫鎖了,而 readerWait 這個字段就是用來記錄這個數量的。 在 Lock 中獲取寫鎖的時候,如果觀測到 readerWait 不為 0 則會阻塞等待,直到 readerWait 為 0 之后才會真正獲取寫鎖,然后才可以進行寫操作。

    讀鎖源碼剖析

    獲取讀鎖的方法如下:

    // 獲取讀鎖
    func (rw *RWMutex) RLock() {
       if rw.readerCount.Add(1) < 0 {
          // 有 writer 在使用鎖,阻塞等待 writer 完成
          runtime_SemacquireRWMutexR(&rw.readerSem, false, 0)
       }
    }

    讀鎖的實現很簡單,先將 readerCount 加 1,如果加 1 之后的值小于 0,說明有 writer 正在使用鎖,那么就需要阻塞等待 writer 完成。

    釋放讀鎖的方法如下:

    // 釋放讀鎖
    func (rw *RWMutex) RUnlock() {
       // readerCount 減 1,如果 readerCount 小于 0 說明有 writer 在等待
       if r := rw.readerCount.Add(-1); r < 0 {
          // 有 writer 在等待,喚醒 writer
          rw.rUnlockSlow(r)
       }
    }
    
    // 喚醒 writer
    func (rw *RWMutex) rUnlockSlow(r int32) {
       // 未 Lock 就 Unlock,panic
       if r+1 == 0 || r+1 == -rwmutexMaxReaders {
          fatal("sync: RUnlock of unlocked RWMutex")
       }
       // readerWait 減 1,返回值是新的 readerWait 值
       if rw.readerWait.Add(-1) == 0 {
          // 最后一個 reader 喚醒 writer
          runtime_Semrelease(&rw.writerSem, false, 1)
       }
    }

    讀鎖的實現總結:

    • 獲取讀鎖的時候,會將 readerCount 加 1

    • 如果正在獲取讀鎖的時候,發現 readerCount 小于 0,說明有 writer 正在使用鎖,那么就需要阻塞等待 writer 完成。

    • 釋放讀鎖的時候,會將 readerCount 減 1

    • 如果 readerCount 減 1 之后小于 0,說明有 writer 正在等待,那么就需要喚醒 writer。

    • 喚醒 writer 的時候,會將 readerWait 減 1,如果 readerWait 減 1 之后為 0,說明 writer 獲取鎖的時候存在的 reader 都已經釋放了讀鎖,可以獲取寫鎖了。

    &middot;rwmutexMaxReaders算是一個特殊的標識,在獲取寫鎖的時候會將readerCount的值減去rwmutexMaxReaders, 所以在其他地方可以根據 readerCount` 是否小于 0 來判斷是否有 writer 正在使用鎖。

    寫鎖源碼剖析

    獲取寫鎖的方法如下:

    // 獲取寫鎖
    func (rw *RWMutex) Lock() {
       // 首先,解決與其他寫入者的競爭。
       rw.w.Lock()
       // 向讀者宣布有一個待處理的寫入。
       // r 就是當前還沒有完成的讀操作,等這部分讀操作完成之后才可以獲取寫鎖。
       r := rw.readerCount.Add(-rwmutexMaxReaders) + rwmutexMaxReaders
       // 等待活躍的 reader
       if r != 0 && rw.readerWait.Add(r) != 0 {
          // 阻塞,等待最后一個 reader 喚醒
          runtime_SemacquireRWMutex(&rw.writerSem, false, 0)
       }
    }

    釋放寫鎖的方法如下:

    // 釋放寫鎖
    func (rw *RWMutex) Unlock() {
       // 向 readers 宣布沒有活動的 writer。
       r := rw.readerCount.Add(rwmutexMaxReaders)
       if r >= rwmutexMaxReaders { // r >= 0 并且 < rwmutexMaxReaders 才是正常的(r 是持有寫鎖期間嘗試獲取讀鎖的 reader 數量)
          fatal("sync: Unlock of unlocked RWMutex")
       }
       // 如果有 reader 在等待寫鎖釋放,那么喚醒這些 reader。
       for i := 0; i < int(r); i++ {
          runtime_Semrelease(&rw.readerSem, false, 0)
       }
       // 允許其他的 writer 繼續進行。
       rw.w.Unlock()
    }

    寫鎖的實現總結:

    • 獲取寫鎖的時候,會將 readerCount 減去 rwmutexMaxReaders,這樣就可以區分讀鎖和寫鎖了。

    • 如果 readerCount 減去 rwmutexMaxReaders 之后不為 0,說明有 reader 正在使用讀鎖,那么就需要阻塞等待這些 reader 釋放讀鎖。

    • 釋放寫鎖的時候,會將 readerCount 加上 rwmutexMaxReaders

    • 如果 readerCount 加上 rwmutexMaxReaders 之后大于 0,說明有 reader 正在等待寫鎖釋放,那么就需要喚醒這些 reader。

    TryRLock 和 TryLock

    TryRLockTryLock 的實現都很簡單,都是嘗試獲取讀鎖或者寫鎖,如果獲取不到就返回 false,獲取到了就返回 true,這兩個方法不會阻塞等待。

    // TryRLock 嘗試鎖定 rw 以進行讀取,并報告是否成功。
    func (rw *RWMutex) TryRLock() bool {
       for {
          c := rw.readerCount.Load()
          // 有 goroutine 持有寫鎖
          if c < 0 {
             return false
          }
          // 嘗試獲取讀鎖
          if rw.readerCount.CompareAndSwap(c, c+1) {
             return true
          }
       }
    }
    
    // TryLock 嘗試鎖定 rw 以進行寫入,并報告是否成功。
    func (rw *RWMutex) TryLock() bool {
       // 寫鎖被占用
       if !rw.w.TryLock() {
          return false
       }
       // 讀鎖被占用
       if !rw.readerCount.CompareAndSwap(0, -rwmutexMaxReaders) {
          // 釋放寫鎖
          rw.w.Unlock()
          return false
       }
       // 成功獲取到鎖
       return true
    }

    到此,相信大家對“Golang中的RWMutex怎么使用”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!

    向AI問一下細節

    免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    AI

    中牟县| 攀枝花市| 馆陶县| 大邑县| 平山县| 湛江市| 阿克苏市| 绥化市| 泗洪县| 黄梅县| 禹州市| 昆明市| 六枝特区| 礼泉县| 花莲县| 石河子市| 钦州市| 栖霞市| 根河市| 高碑店市| 庆城县| 津市市| 绥中县| 田东县| 余姚市| 岳普湖县| 监利县| 哈巴河县| 大庆市| 腾冲县| 达尔| 社会| 黄大仙区| 陈巴尔虎旗| 广河县| 岐山县| 且末县| 西乌珠穆沁旗| 汉源县| 江陵县| 珲春市|