Go語言中的互斥鎖和讀寫鎖(Mutex和RWMutex)

雪山飛豬發表於2020-11-03


雖然Go語言提供channel來保證協程的通訊,但是某些場景用鎖來顯示保證協程的安全更清晰易懂。
Go語言中主要有兩種鎖,互斥鎖Mutex和讀寫鎖RWMutex,下面分別介紹一下使用方法,以及出現死鎖的常見場景。

一、Mutex(互斥鎖)

Mutex是互斥鎖的意思,也叫排他鎖,同一時刻一段程式碼只能被一個執行緒執行,使用只需要關注方法Lock(加鎖)和Unlock(解鎖)即可。
在Lock()和Unlock()之間的程式碼段稱為資源的臨界區(critical section),是執行緒安全的,任何一個時間點都只能有一個goroutine執行這段區間的程式碼。

不加鎖示例

先來一段不加群的程式碼,10個協程同時累加1萬

package main

import (
    "fmt"
    "sync"
)

func main() {
    var count = 0
    var wg sync.WaitGroup
    //十個協程數量
    n := 10
    wg.Add(n)
    for i := 0; i < n; i++ {
        go func() {
            defer wg.Done()
            //1萬疊加
            for j := 0; j < 10000; j++ {
                count++
            }
        }()
    }
    wg.Wait()
    fmt.Println(count)
}

執行結果如下

38532

正確的結果應該是100000,這裡出現了併發寫入更新錯誤的情況

加鎖示例

我們再新增鎖,程式碼如下

package main

import (
    "fmt"
    "sync"
)

func main() {
    var count = 0
    var wg sync.WaitGroup
    var mu sync.Mutex
    //十個協程數量
    n := 10
    wg.Add(n)
    for i := 0; i < n; i++ {
        go func() {
            defer wg.Done()
            //1萬疊加
            for j := 0; j < 10000; j++ {
                mu.Lock()
                count++
                mu.Unlock()
            }
        }()
    }
    wg.Wait()
    fmt.Println(count)
}

執行結果如下,可以看到,已經看到結果變成了正確的100000

二、RWMutex(讀寫鎖)

Mutex在大量併發的情況下,會造成鎖等待,對效能的影響比較大。
如果某個讀操作的協程加了鎖,其他的協程沒必要處於等待狀態,可以併發地訪問共享變數,這樣能讓讀操作並行,提高讀效能。
RWLock就是用來幹這個的,這種鎖在某一時刻能由什麼問題數量的reader持有,或者被一個wrtier持有

主要遵循以下規則 :

  1. 讀寫鎖的讀鎖可以重入,在已經有讀鎖的情況下,可以任意加讀鎖。
  2. 在讀鎖沒有全部解鎖的情況下,寫操作會阻塞直到所有讀鎖解鎖。
  3. 寫鎖定的情況下,其他協程的讀寫都會被阻塞,直到寫鎖解鎖。

Go語言的讀寫鎖方法主要有下面這種

  1. Lock/Unlock:針對寫操作。
    不管鎖是被reader還是writer持有,這個Lock方法會一直阻塞,Unlock用來釋放鎖的方法
  2. RLock/RUnlock:針對讀操作
    當鎖被reader所有的時候,RLock會直接返回,當鎖已經被writer所有,RLock會一直阻塞,直到能獲取鎖,否則就直接返回,RUnlock用來釋放鎖的方法

併發讀示例

package main

import (
    "fmt"
    "sync"
    "time"
)

func main() {
    var m sync.RWMutex
    go read(&m, 1)
    go read(&m, 2)
    go read(&m, 3)

    time.Sleep(2 * time.Second)
}

func read(m *sync.RWMutex, i int) {
    fmt.Println(i, "reader start")
    m.RLock()
    fmt.Println(i, "reading")
    time.Sleep(1 * time.Second)
    m.RUnlock()

    fmt.Println(i, "reader over")
}

執行如下

可以看到,3的讀還沒結束,1和2已經開始讀了

併發讀寫示例

package main

import (
    "fmt"
    "sync"
    "time"
)

var count = 0

func main() {
    var m sync.RWMutex
    for i := 1; i <= 3; i++ {
        go write(&m, i)
    }
    for i := 1; i <= 3; i++ {
        go read(&m, i)
    }

    time.Sleep(1 * time.Second)
    fmt.Println("final count:", count)
}

func read(m *sync.RWMutex, i int) {
    fmt.Println(i, "reader start")
    m.RLock()
    fmt.Println(i, "reading count:", count)
    time.Sleep(1 * time.Millisecond)
    m.RUnlock()

    fmt.Println(i, "reader over")
}

func write(m *sync.RWMutex, i int) {
    fmt.Println(i, "writer start")
    m.Lock()
    count++
    fmt.Println(i, "writing count", count)
    time.Sleep(1 * time.Millisecond)
    m.Unlock()

    fmt.Println(i, "writer over")
}

執行結果如下

如果我們可以明確區分reader和writer的協程場景,且是大師的併發讀、少量的併發寫,有強烈的效能需要,我們就可以考慮使用讀寫鎖RWMutex替換Mutex

三、死鎖場景

當兩個或兩個以上的程式在執行過程中,因爭奪資源而處理一種互相等待的狀態,如果沒有外部干涉無法繼續下去,這時我們稱系統處於死鎖或產生了死鎖。
死鎖主要有以下幾種場景。

Lock/Unlock不是成對出現

沒有成對出現容易會出現死鎖的情況,或者是Unlock 一個未加鎖的Mutex而導致 panic,程式碼建議以下面緊湊的方式出現

mu.Lock()
defer mu.Unlock()

鎖被拷貝使用

package main

import (
    "fmt"
    "sync"
)

func main() {
    var mu sync.Mutex
    mu.Lock()
    defer mu.Unlock()
    copyTest(mu)
}

//這裡複製了一個鎖,造成了死鎖
func copyTest(mu sync.Mutex) {
    mu.Lock()
    defer mu.Unlock()
    fmt.Println("ok")
}

在函式外層已經加了一個Lock,在拷貝的時候又執行了一次Lock,因此這是一個永遠不會獲得的鎖,因為外層函式的Unlock無法執行。

迴圈等待

A等待B,B等待C,C等待A,陷入了無限迴圈(哲學家就餐問題)

package main

import (
    "sync"
)

func main() {
    var muA, muB sync.Mutex
    var wg sync.WaitGroup

    wg.Add(2)
    go func() {
        defer wg.Done()
        muA.Lock()
        defer muA.Unlock()
        //A依賴B
        muB.Lock()
        defer muB.Lock()
    }()

    go func() {
        defer wg.Done()
        muB.Lock()
        defer muB.Lock()
        //B依賴A
        muA.Lock()
        defer muA.Unlock()
    }()
    wg.Wait()
}

以上就是Go語言的鎖使用,由chenqionghe傾情整理,giao~

相關文章